Merge "Remove internal display related methods"
diff --git a/Android.bp b/Android.bp
index 2c550fd..aa65486 100644
--- a/Android.bp
+++ b/Android.bp
@@ -213,7 +213,7 @@
         "android.hardware.radio.data-V1-java",
         "android.hardware.radio.messaging-V1-java",
         "android.hardware.radio.modem-V1-java",
-        "android.hardware.radio.network-V1-java",
+        "android.hardware.radio.network-V2-java",
         "android.hardware.radio.sim-V1-java",
         "android.hardware.radio.voice-V1-java",
         "android.hardware.thermal-V1.0-java-constants",
@@ -342,6 +342,14 @@
         "staledataclass-annotation-processor",
         "error_prone_android_framework",
     ],
+    // Exports needed for staledataclass-annotation-processor, see b/139342589.
+    javacflags: [
+        "-J--add-modules=jdk.compiler",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
     required: [
         // TODO(b/120066492): remove default_television.xml when the build system
         // propagates "required" properly.
diff --git a/ApiDocs.bp b/ApiDocs.bp
index c87ecde..4bba338 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -84,6 +84,7 @@
         ":framework-connectivity-sources",
         ":framework-bluetooth-sources",
         ":framework-connectivity-tiramisu-updatable-sources",
+        ":framework-federatedcompute-sources",
         ":framework-graphics-srcs",
         ":framework-mediaprovider-sources",
         ":framework-nearby-sources",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index c12f5b4..56d91b2 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -41,7 +41,6 @@
         ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
     output_extension: "srcjar",
 }
@@ -72,7 +71,6 @@
         ":libstats_atom_message_protos",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
 
     output_extension: "proto.h",
@@ -91,7 +89,6 @@
         "cmds/statsd/src/**/*.proto",
         "core/proto/**/*.proto",
         "libs/incident/proto/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
     proto: {
         include_dirs: [
@@ -126,7 +123,6 @@
         ":libstats_atom_message_protos",
         "core/proto/**/*.proto",
         "libs/incident/proto/android/os/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
     // Protos have lots of MissingOverride and similar.
     errorprone: {
@@ -148,7 +144,6 @@
         ":libstats_atom_message_protos",
         "core/proto/**/*.proto",
         "libs/incident/proto/android/os/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
     exclude_srcs: [
         "core/proto/android/privacy.proto",
@@ -184,7 +179,6 @@
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
         "core/proto/**/*.proto",
-        ":service-permission-streaming-proto-sources",
     ],
 }
 
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index bb57161..4e24909 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -16,6 +16,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <profileable android:shell="true" />
         <activity android:name="android.perftests.utils.PerfTestActivity"
             android:exported="true">
           <intent-filter>
diff --git a/apct-tests/perftests/core/AndroidTest.xml b/apct-tests/perftests/core/AndroidTest.xml
index 4f8ee29..86f41e1 100644
--- a/apct-tests/perftests/core/AndroidTest.xml
+++ b/apct-tests/perftests/core/AndroidTest.xml
@@ -27,6 +27,11 @@
         <option name="test-file-name" value="CorePerfTests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
+    </target_preparer>
+
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="pull-pattern-keys" value="perfetto_file_path" />
     </metrics_collector>
diff --git a/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml b/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml
new file mode 100644
index 0000000..9005d6f
--- /dev/null
+++ b/apct-tests/perftests/core/res/layout/linear_layout_for_xmlblock_benchmark.xml
@@ -0,0 +1,110 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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/linear_layout_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="10dp" >
+
+    <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
+        android:id="@+id/remote_input_text"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_weight="1"
+        android:paddingTop="14dp"
+        android:paddingStart="4dp"
+        android:paddingBottom="16dp"
+        android:paddingEnd="12dp"
+        android:layout_gravity="start|center_vertical"
+        android:textAppearance="?android:attr/textAppearance"
+        android:textSize="16sp"
+        android:background="@null"
+        android:maxLines="4"
+        android:ellipsize="start"
+        android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
+        android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
+
+    <ImageButton
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        style="@android:style/MediaButton.Previous"
+        id="@+id/my_image_button_previous"
+        />
+
+    <ImageButton
+        id="@+id/my_image_button_next"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        style="@android:style/MediaButton.Next"
+        />
+
+    <LinearLayout
+        id="@+id/my_linear_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="10dp" >
+
+        <ImageButton
+            android:id="@+id/button3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true" />
+
+        <LinearLayout
+            id="@+id/my_inner_linear_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:padding="10dp" >
+
+            <ImageButton
+                android:id="@+id/button5"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_alignParentBottom="true" />
+        </LinearLayout>
+
+        <ImageButton
+            android:id="@+id/button4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignBottom="@+id/button2"
+            android:layout_centerHorizontal="true" />
+
+        <ImageButton
+            android:id="@+id/button6"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_above="@+id/button4"
+            android:layout_centerHorizontal="true" />
+
+        <ImageButton
+            android:id="@+id/button7"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignBottom="@+id/button"
+            android:layout_toEndOf="@+id/button"
+            android:layout_toRightOf="@+id/button" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java b/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java
new file mode 100644
index 0000000..bdce902
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/content/res/XmlBlockBenchmark.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.perftests.core.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@LargeTest
+public class XmlBlockBenchmark {
+    private static final String TAG = "XmlBlockBenchmark";
+    private static final String NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
+
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private XmlBlock.Parser mParser;
+
+    private void cleanCache() {
+        if (mParser != null) {
+            mParser.close();
+        }
+
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final Resources resources = context.getResources();
+        resources.getImpl().clearAllCaches();
+        Log.d(TAG, "cleanCache");
+    }
+
+    private XmlBlock.Parser getNewParser() {
+        cleanCache();
+
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final Resources resources = context.getResources();
+        return (XmlBlock.Parser) resources.getXml(R.layout.linear_layout_for_xmlblock_benchmark);
+    }
+
+    @Before
+    public void setUp() {
+        mParser = getNewParser();
+    }
+
+    @After
+    public void tearDown() {
+        cleanCache();
+    }
+
+    int safeNext() throws XmlPullParserException, IOException {
+        while (true) {
+            int parseState = mParser.next();
+            if (parseState == START_TAG) {
+                return parseState;
+            } else if (parseState == END_DOCUMENT) {
+                mParser = getNewParser();
+            }
+        }
+    }
+
+    @Test
+    public void throwNpeCausedByNullDocument() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        mParser.close();
+        while (state.keepRunning()) {
+            try {
+                mParser.getClassAttribute();
+            } catch (NullPointerException e) {
+                continue;
+            }
+            Assert.fail("It shouldn't be here!");
+        }
+    }
+
+    @Test
+    public void getNext() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            int parseState = mParser.next();
+            state.pauseTiming();
+            if (parseState == END_DOCUMENT) {
+                mParser = getNewParser();
+            }
+            state.resumeTiming();
+        }
+    }
+
+    private <T> void benchmarkTagFunction(BenchmarkState state, String name,
+            Supplier<T> measureTarget)
+            throws XmlPullParserException, IOException {
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            int parseState = safeNext();
+
+            if (parseState != END_DOCUMENT) {
+                final String tagName = mParser.getName();
+                state.resumeTiming();
+                final T value = measureTarget.get();
+                state.pauseTiming();
+                Log.d(TAG,
+                        TextUtils.formatSimple("%s() in tag %s is %s", name, tagName, value));
+            }
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void getNamespace() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getNamespace", () -> mParser.getNamespace());
+    }
+
+    @Test
+    public void getName() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getName", () -> mParser.getName());
+    }
+
+    @Test
+    public void getText() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getText", () -> mParser.getText());
+    }
+
+    @Test
+    public void getLineNumber() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getLineNumber", () -> mParser.getLineNumber());
+    }
+
+    @Test
+    public void getAttributeCount() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getAttributeCount", () -> mParser.getAttributeCount());
+    }
+
+    private <T> void benchmarkAttributeFunction(BenchmarkState state, String name,
+            Function<Integer, T> measureTarget)
+            throws XmlPullParserException, IOException {
+        boolean needNext = true;
+        boolean needGetCount = false;
+        int attributeCount = 0;
+        int i = 0;
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (needNext) {
+                int parseState = safeNext();
+                if (parseState == START_TAG) {
+                    needNext = false;
+                    needGetCount = true;
+                }
+            }
+
+            if (needGetCount) {
+                attributeCount = mParser.getAttributeCount();
+                needGetCount = false;
+                i = 0;
+            }
+
+            if (i < attributeCount) {
+                final String tagName = mParser.getName();
+                final String attributeName = mParser.getAttributeName(i);
+                state.resumeTiming();
+                final T value = measureTarget.apply(i);
+                state.pauseTiming();
+                Log.d(TAG,
+                        TextUtils.formatSimple("%s(%d:%s) in tag %s is %s", name, i, attributeName,
+                                tagName, value));
+                i++;
+            }
+
+            if (i >= attributeCount) {
+                needNext = true;
+            }
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void getAttributeNamespace() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkAttributeFunction(state, "getAttributeNamespace",
+                attributeIndex -> mParser.getAttributeNamespace(attributeIndex));
+    }
+
+    @Test
+    public void getAttributeName() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkAttributeFunction(state, "getAttributeName",
+                attributeIndex -> mParser.getAttributeName(attributeIndex));
+    }
+
+    @Test
+    public void getAttributeNameResource() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkAttributeFunction(state, "getAttributeNameResource",
+                attributeIndex -> mParser.getAttributeNameResource(attributeIndex));
+    }
+
+    /**
+     * benchmark {@link android.content.res.XmlBlock#nativeGetAttributeDataType(long, int)} and
+     * {@link android.content.res.XmlBlock#nativeGetAttributeData(long, int)}
+     */
+    @Test
+    public void getAttributeDataXXX() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkAttributeFunction(state, "getAttributeDataXXX",
+                attributeIndex -> mParser.getAttributeValue(attributeIndex));
+    }
+
+    @Test
+    public void getSourceResId() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getSourceResId", () -> mParser.getSourceResId());
+    }
+
+    @Test
+    public void getIdAttribute() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getIdAttribute", () -> mParser.getIdAttribute());
+    }
+
+    @Test
+    public void getClassAttribute() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getClassAttribute", () -> mParser.getClassAttribute());
+    }
+
+    @Test
+    public void getStyleAttribute() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getStyleAttribute", () -> mParser.getStyleAttribute());
+    }
+
+    @Test
+    public void getAttributeIndex() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        benchmarkTagFunction(state, "getAttributeValue",
+                () -> mParser.getAttributeValue(NAMESPACE_ANDROID, "layout_width"));
+    }
+
+    @Test
+    public void parseOneXmlDocument() throws XmlPullParserException, IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            mParser = getNewParser();
+            state.resumeTiming();
+
+            int parseState;
+            while ((parseState = mParser.next()) != END_DOCUMENT) {
+                if (parseState == START_DOCUMENT) {
+                    state.pauseTiming();
+                    Log.d(TAG, "parseOneXmlDocument: start document");
+                    state.resumeTiming();
+                } else if (parseState == START_TAG) {
+                    final String tagName = mParser.getName();
+                    state.pauseTiming();
+                    Log.d(TAG, TextUtils.formatSimple("parseOneXmlDocument: tag %s {[", tagName));
+                    state.resumeTiming();
+                    for (int i = 0, count = mParser.getAttributeCount(); i < count; i++) {
+                        final String attributeName = mParser.getAttributeName(i);
+                        final String attributeValue = mParser.getAttributeValue(i);
+
+                        state.pauseTiming();
+                        Log.d(TAG, TextUtils.formatSimple(
+                                "parseOneXmlDocument: attribute %d {%s = %s},", i, attributeName,
+                                attributeValue));
+                        state.resumeTiming();
+                    }
+                    state.pauseTiming();
+                    Log.d(TAG, "parseOneXmlDocument: ]");
+                    state.resumeTiming();
+                } else if (parseState == END_TAG) {
+                    state.pauseTiming();
+                    Log.d(TAG, "parseOneXmlDocument: }");
+                    state.resumeTiming();
+                } else {
+                    final String text = mParser.getText();
+                    state.pauseTiming();
+                    Log.d(TAG, TextUtils.formatSimple(
+                            "parseOneXmlDocument: parseState = %d, text = %s", parseState, text));
+                    state.resumeTiming();
+                }
+            }
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java b/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java
new file mode 100644
index 0000000..b296148
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.opengl.perftests;
+
+import android.opengl.Matrix;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Random;
+
+@LargeTest
+public class MatrixPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Rule
+    public float[] array = new float[48];
+
+    @Rule
+    public float[] bigArray = new float[16 * 1024 * 1024];
+
+
+    @Test
+    public void testMultiplyMM() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(array, 32, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMMLeftOverlapResult() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(array, 16, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMMRightOverlapResult() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(array, 0, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMMAllOverlap() {
+        Random rng = new Random();
+        for (int i = 0; i < 16; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(array, 0, array, 0, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMMOutputBigArray() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(bigArray, 1024, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMMAllBigArray() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            bigArray[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMM(bigArray, 1024, bigArray, 16, bigArray, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMV() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(array, 20, array, 4, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMVLeftOverlapResult() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(array, 4, array, 4, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMVRightOverlapResult() {
+        Random rng = new Random();
+        for (int i = 0; i < 32; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(array, 0, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMVAllOverlap() {
+        Random rng = new Random();
+        for (int i = 0; i < 16; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(array, 0, array, 0, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMVOutputBigArray() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(bigArray, 1024, array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testMultiplyMVAllBigArray() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            bigArray[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.multiplyMV(bigArray, 1024, bigArray, 16, bigArray, 0);
+        }
+    }
+
+    @Test
+    public void testTransposeM() {
+        Random rng = new Random();
+        for (int i = 0; i < 16; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.transposeM(array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testInvertM() {
+        // non-singular matrix
+        array[ 0] =  0.814f;
+        array[ 1] =  4.976f;
+        array[ 2] = -3.858f;
+        array[ 3] =  7.206f;
+        array[ 4] =  5.112f;
+        array[ 5] = -2.420f;
+        array[ 6] =  8.791f;
+        array[ 7] =  6.426f;
+        array[ 8] =  2.945f;
+        array[ 9] =  1.801f;
+        array[10] = -2.594f;
+        array[11] =  2.663f;
+        array[12] = -5.003f;
+        array[13] = -4.188f;
+        array[14] =  3.340f;
+        array[15] = -1.235f;
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.invertM(array, 16, array, 0);
+        }
+    }
+
+    @Test
+    public void testOrthoM() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.orthoM(array, 0, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+        }
+    }
+
+    @Test
+    public void testFrustumM() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.frustumM(array, 0, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+        }
+    }
+
+    @Test
+    public void testPerspectiveM() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.perspectiveM(array, 0, 45.0f, 1.0f, 1.0f, 100.0f);
+        }
+    }
+
+    @Test
+    public void testLength() {
+        Random rng = new Random();
+        for (int i = 0; i < 3; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.length(array[0], array[1], array[2]);
+        }
+    }
+
+    @Test
+    public void testSetIdentityM() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.setIdentityM(array, 0);
+        }
+    }
+
+    @Test
+    public void testScaleM() {
+        Random rng = new Random();
+        for (int i = 0; i < 19; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.scaleM(array, 19, array, 0, array[16], array[17], array[18]);
+        }
+    }
+
+    @Test
+    public void testScaleMInPlace() {
+        Random rng = new Random();
+        for (int i = 0; i < 19; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.scaleM(array, 0, array[16], array[17], array[18]);
+        }
+    }
+
+    @Test
+    public void testTranslateM() {
+        Random rng = new Random();
+        for (int i = 0; i < 19; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.translateM(array, 19, array, 0, array[16], array[17], array[18]);
+        }
+    }
+
+    @Test
+    public void testTranslateMInPlace() {
+        Random rng = new Random();
+        for (int i = 0; i < 19; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.translateM(array, 0, array[16], array[17], array[18]);
+        }
+    }
+
+    @Test
+    public void testRotateM() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.rotateM(array, 20, array, 0, array[16], array[17], array[18], array[19]);
+        }
+    }
+
+    @Test
+    public void testRotateMInPlace() {
+        Random rng = new Random();
+        for (int i = 0; i < 20; i++) {
+            array[i] = rng.nextFloat();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.rotateM(array, 0, array[16], array[17], array[18], array[19]);
+        }
+    }
+
+    @Test
+    public void testSetRotateM() {
+        Random rng = new Random();
+        array[0] = rng.nextFloat() * 90.0f;
+        array[1] = rng.nextFloat() + 0.5f;
+        array[2] = rng.nextFloat() + 0.5f;
+        array[3] = rng.nextFloat() + 0.5f;
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.setRotateM(array, 4, array[0], array[1], array[2], array[3]);
+        }
+    }
+
+    @Test
+    public void testSetRotateEulerM() {
+        Random rng = new Random();
+        array[0] = rng.nextFloat() * 90.0f;
+        array[1] = rng.nextFloat() * 90.0f;
+        array[2] = rng.nextFloat() * 90.0f;
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.setRotateEulerM(array, 3, array[0], array[1], array[2]);
+        }
+    }
+
+    @Test
+    public void testSetLookAtM() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Matrix.setLookAtM(array, 9,
+                    1.0f, 0.0f, 0.0f,
+                    1.0f, 0.0f, 1.0f,
+                    0.0f, 1.0f, 0.0f);
+        }
+    }
+}
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 3452f58..6d1e6d0 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -28,16 +28,16 @@
 import com.android.internal.util.ConcurrentUtils
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
 import libcore.io.IoUtils
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import java.io.File
-import java.io.FileOutputStream
-import java.util.concurrent.ArrayBlockingQueue
-import java.util.concurrent.TimeUnit
 
 @LargeTest
 @RunWith(Parameterized::class)
@@ -180,8 +180,8 @@
         protected abstract fun parseImpl(file: File): PackageType
     }
 
-    class ParallelParser1(private val cacher: PackageCacher1? = null)
-        : ParallelParser<PackageParser.Package>(cacher) {
+    class ParallelParser1(private val cacher: PackageCacher1? = null) :
+        ParallelParser<PackageParser.Package>(cacher) {
         val parser = PackageParser().apply {
             setCallback { true }
         }
@@ -189,8 +189,8 @@
         override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
     }
 
-    class ParallelParser2(cacher: PackageCacher2? = null)
-        : ParallelParser<PackageImpl>(cacher) {
+    class ParallelParser2(cacher: PackageCacher2? = null) :
+        ParallelParser<PackageImpl>(cacher) {
         val input = ThreadLocal.withInitial {
             // For testing, just disable enforcement to avoid hooking up to compat framework
             ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
@@ -218,7 +218,7 @@
             })
 
         override fun parseImpl(file: File) =
-                parser.parsePackage(input.get()!!.reset(), file, 0, null).result
+                parser.parsePackage(input.get()!!.reset(), file, 0).result
                         as PackageImpl
     }
 
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index be0e025..299ad66 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -91,6 +91,13 @@
         }
     }
 
+    public static final String KEY_ENABLE_TARE = "enable_tare";
+    public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm";
+    public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job";
+    public static final boolean DEFAULT_ENABLE_TARE = true;
+    public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true;
+    public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true;
+
     // Keys for AlarmManager TARE factors
     /** @hide */
     public static final String KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED =
@@ -227,6 +234,9 @@
     public static final String KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP =
             "js_min_satiated_balance_other_app";
     /** @hide */
+    public static final String KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER =
+            "js_min_satiated_balance_increment_updater";
+    /** @hide */
     public static final String KEY_JS_MAX_SATIATED_BALANCE =
             "js_max_satiated_balance";
     /** @hide */
@@ -509,6 +519,15 @@
     public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0);
     /** @hide */
     public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000);
+    /**
+     * How many credits to increase the updating app's min satiated balance by for each app that it
+     * is responsible for updating.
+     * @hide
+     */
+    public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES =
+            // Research indicates that the median time between popular app updates is 13-14 days,
+            // so adjust by 14 to amortize over that time.
+            DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES / 14;
     /** @hide */
     public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3);
     /** @hide */
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 5b61f9a..901796b 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -39,6 +39,9 @@
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 
+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;
@@ -56,6 +59,8 @@
 import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_UNDEFINED;
 
 import android.Manifest;
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -85,7 +90,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -99,7 +103,6 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.ThreadLocalWorkSource;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -126,6 +129,7 @@
 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;
@@ -142,8 +146,12 @@
 import com.android.server.EventLogTags;
 import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemClockTime;
+import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
+import com.android.server.SystemTimeZone;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -199,7 +207,6 @@
     static final boolean DEBUG_TARE = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
-    static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     static final int TICK_HISTORY_DEPTH = 10;
     static final long INDEFINITE_DELAY = 365 * INTERVAL_DAY;
@@ -213,6 +220,19 @@
 
     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);
 
@@ -289,6 +309,7 @@
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     IBinder.DeathRecipient mListenerDeathRecipient;
     Intent mTimeTickIntent;
+    Bundle mTimeTickOptions;
     IAlarmListener mTimeTickTrigger;
     PendingIntent mDateChangeSender;
     boolean mInteractive = true;
@@ -863,9 +884,11 @@
             mInjector.registerDeviceConfigListener(this);
             final EconomyManagerInternal economyManagerInternal =
                     LocalServices.getService(EconomyManagerInternal.class);
-            economyManagerInternal.registerTareStateChangeListener(this);
+            economyManagerInternal.registerTareStateChangeListener(this,
+                    AlarmManagerEconomicPolicy.POLICY_ALARM);
             onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ALARM_MANAGER));
-            updateTareSettings(economyManagerInternal.isEnabled());
+            updateTareSettings(
+                    economyManagerInternal.isEnabled(AlarmManagerEconomicPolicy.POLICY_ALARM));
         }
 
         public void updateAllowWhileIdleWhitelistDurationLocked() {
@@ -1397,7 +1420,7 @@
 
     private long convertToElapsed(long when, int type) {
         if (isRtc(type)) {
-            when -= mInjector.getCurrentTimeMillis() - mInjector.getElapsedRealtime();
+            when -= mInjector.getCurrentTimeMillis() - mInjector.getElapsedRealtimeMillis();
         }
         return when;
     }
@@ -1557,7 +1580,7 @@
             alarmsToDeliver = alarmsForUid;
             mPendingBackgroundAlarms.remove(uid);
         }
-        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, mInjector.getElapsedRealtime());
+        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, mInjector.getElapsedRealtimeMillis());
     }
 
     /**
@@ -1574,7 +1597,8 @@
                 mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
 
         if (alarmsToDeliver.size() > 0) {
-            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, mInjector.getElapsedRealtime());
+            deliverPendingBackgroundAlarmsLocked(
+                    alarmsToDeliver, mInjector.getElapsedRealtimeMillis());
         }
     }
 
@@ -1881,22 +1905,16 @@
 
             mNextWakeup = mNextNonWakeup = 0;
 
-            // We have to set current TimeZone info to kernel
-            // because kernel doesn't keep this after reboot
-            setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
-
-            // Ensure that we're booting with a halfway sensible current time.  Use the
-            // most recent of Build.TIME, the root file system's timestamp, and the
-            // value of the ro.build.date.utc system property (which is in seconds).
-            final long systemBuildTime = Long.max(
-                    1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
-                    Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
-            if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
-                Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
-                        + ", advancing to build time " + systemBuildTime);
-                mInjector.setKernelTime(systemBuildTime);
+            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();
+
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
             // Determine SysUI's uid
             mSystemUiUid = mInjector.getSystemUiUid(mPackageManagerInternal);
@@ -1909,7 +1927,9 @@
                     Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-
+            mTimeTickOptions = BroadcastOptions
+                    .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+                    .toBundle();
             mTimeTickTrigger = new IAlarmListener.Stub() {
                 @Override
                 public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException {
@@ -1921,8 +1941,8 @@
                     // takes care of this automatically, but we're using the direct internal
                     // interface here rather than that client-side wrapper infrastructure.
                     mHandler.post(() -> {
-                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL);
-
+                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL, null,
+                                mTimeTickOptions);
                         try {
                             callback.alarmComplete(this);
                         } catch (RemoteException e) { /* local method call */ }
@@ -2108,22 +2128,24 @@
         }
     }
 
-    boolean setTimeImpl(long millis) {
-        if (!mInjector.isAlarmDriverPresent()) {
-            Slog.w(TAG, "Not setting time since no alarm driver is available.");
-            return false;
-        }
-
+    boolean setTimeImpl(
+            @CurrentTimeMillisLong long newSystemClockTimeMillis, @TimeConfidence int confidence,
+            @NonNull String logMsg) {
         synchronized (mLock) {
-            final long currentTimeMillis = mInjector.getCurrentTimeMillis();
-            mInjector.setKernelTime(millis);
-            final TimeZone timeZone = TimeZone.getDefault();
-            final int currentTzOffset = timeZone.getOffset(currentTimeMillis);
-            final int newTzOffset = timeZone.getOffset(millis);
-            if (currentTzOffset != newTzOffset) {
-                Slog.i(TAG, "Timezone offset has changed, updating kernel timezone");
-                mInjector.setKernelTimezone(-(newTzOffset / 60000));
+            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.
@@ -2131,31 +2153,30 @@
         }
     }
 
-    void setTimeZoneImpl(String tz) {
-        if (TextUtils.isEmpty(tz)) {
+    void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence, String logInfo) {
+        if (TextUtils.isEmpty(tzId)) {
             return;
         }
 
-        TimeZone zone = TimeZone.getTimeZone(tz);
+        TimeZone newZone = TimeZone.getTimeZone(tzId);
         // Prevent reentrant calls from stepping on each other when writing
         // the time zone property
-        boolean timeZoneWasChanged = false;
+        boolean timeZoneWasChanged;
         synchronized (this) {
-            String current = SystemProperties.get(TIMEZONE_PROPERTY);
-            if (current == null || !current.equals(zone.getID())) {
-                if (localLOGV) {
-                    Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
-                }
-                timeZoneWasChanged = true;
-                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
-            }
+            // TimeZone.getTimeZone() can return a time zone with a different ID (e.g. it can return
+            // "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);
 
-            // Update the kernel timezone information
-            // Kernel tracks time offsets as 'minutes west of GMT'
-            int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis());
-            mInjector.setKernelTimezone(-(gmtOffset / 60000));
+            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
+        // to TimeZone.getDefault() to re-read the device settings.
         TimeZone.setDefault(null);
 
         if (timeZoneWasChanged) {
@@ -2168,7 +2189,7 @@
                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                     | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                     | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-            intent.putExtra(Intent.EXTRA_TIMEZONE, zone.getID());
+            intent.putExtra(Intent.EXTRA_TIMEZONE, newZone.getID());
             mOptsTimeBroadcast.setTemporaryAppAllowlist(
                     mActivityManagerInternal.getBootTimeTempAllowListDuration(),
                     TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
@@ -2231,7 +2252,7 @@
             triggerAtTime = 0;
         }
 
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         final long nominalTrigger = convertToElapsed(triggerAtTime, type);
         // Try to prevent spamming by making sure apps aren't firing alarms in the immediate future
         final long minTrigger = nowElapsed
@@ -2354,7 +2375,7 @@
             // No need to fuzz as this is already earlier than the coming wake-from-idle.
             return changedBeforeFuzz;
         }
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         final long futurity = upcomingWakeFromIdle - nowElapsed;
 
         if (futurity <= mConstants.MIN_DEVICE_IDLE_FUZZ) {
@@ -2376,7 +2397,7 @@
      * @return {@code true} if the alarm delivery time was updated.
      */
     private boolean adjustDeliveryTimeBasedOnBatterySaver(Alarm alarm) {
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         if (isExemptFromBatterySaver(alarm)) {
             return false;
         }
@@ -2443,7 +2464,7 @@
      * @return {@code true} if the alarm delivery time was updated.
      */
     private boolean adjustDeliveryTimeBasedOnDeviceIdle(Alarm alarm) {
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         if (mPendingIdleUntil == null || mPendingIdleUntil == alarm) {
             return alarm.setPolicyElapsed(DEVICE_IDLE_POLICY_INDEX, nowElapsed);
         }
@@ -2498,7 +2519,7 @@
      *         adjustments made in this call.
      */
     private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         if (mConstants.USE_TARE_POLICY || isExemptFromAppStandby(alarm) || mAppStandbyParole) {
             return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
         }
@@ -2558,7 +2579,7 @@
      * adjustments made in this call.
      */
     private boolean adjustDeliveryTimeBasedOnTareLocked(Alarm alarm) {
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         if (!mConstants.USE_TARE_POLICY
                 || isExemptFromTare(alarm) || hasEnoughWealthLocked(alarm)) {
             return alarm.setPolicyElapsed(TARE_POLICY_INDEX, nowElapsed);
@@ -2614,7 +2635,7 @@
                 ent.pkg = a.sourcePackage;
                 ent.tag = a.statsTag;
                 ent.op = "START IDLE";
-                ent.elapsedRealtime = mInjector.getElapsedRealtime();
+                ent.elapsedRealtime = mInjector.getElapsedRealtimeMillis();
                 ent.argRealtime = a.getWhenElapsed();
                 mAllowWhileIdleDispatches.add(ent);
             }
@@ -2685,6 +2706,19 @@
         }
 
         @Override
+        public void setTimeZone(String tzId, @TimeZoneConfidence int confidence,
+                String logInfo) {
+            setTimeZoneImpl(tzId, confidence, logInfo);
+        }
+
+        @Override
+        public void setTime(
+                @CurrentTimeMillisLong long unixEpochTimeMillis, int confidence,
+                String logMsg) {
+            setTimeImpl(unixEpochTimeMillis, confidence, logMsg);
+        }
+
+        @Override
         public void registerInFlightListener(InFlightListener callback) {
             synchronized (mLock) {
                 mInFlightListeners.add(callback);
@@ -2953,12 +2987,16 @@
         }
 
         @Override
-        public boolean setTime(long millis) {
+        public boolean setTime(@CurrentTimeMillisLong long millis) {
             getContext().enforceCallingOrSelfPermission(
                     "android.permission.SET_TIME",
                     "setTime");
 
-            return setTimeImpl(millis);
+            // 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
+            // the user or a developer, confidence is assumed "high".
+            final int timeConfidence = TIME_CONFIDENCE_HIGH;
+            return setTimeImpl(millis, timeConfidence, "AlarmManager.setTime() called");
         }
 
         @Override
@@ -2969,7 +3007,11 @@
 
             final long oldId = Binder.clearCallingIdentity();
             try {
-                setTimeZoneImpl(tz);
+                // The public API (and the shell command that also uses this method) have no concept
+                // of confidence, but since the time zone ID should come either from apps working on
+                // behalf of the user or a developer, confidence is assumed "high".
+                final int timeZoneConfidence = TIME_ZONE_CONFIDENCE_HIGH;
+                setTimeZoneImpl(tz, timeZoneConfidence, "AlarmManager.setTimeZone() called");
             } finally {
                 Binder.restoreCallingIdentity(oldId);
             }
@@ -3104,7 +3146,7 @@
                 pw.println();
             }
 
-            final long nowELAPSED = mInjector.getElapsedRealtime();
+            final long nowELAPSED = mInjector.getElapsedRealtimeMillis();
             final long nowUPTIME = SystemClock.uptimeMillis();
             final long nowRTC = mInjector.getCurrentTimeMillis();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@@ -3580,7 +3622,7 @@
 
         synchronized (mLock) {
             final long nowRTC = mInjector.getCurrentTimeMillis();
-            final long nowElapsed = mInjector.getElapsedRealtime();
+            final long nowElapsed = mInjector.getElapsedRealtimeMillis();
             proto.write(AlarmManagerServiceDumpProto.CURRENT_TIME, nowRTC);
             proto.write(AlarmManagerServiceDumpProto.ELAPSED_REALTIME, nowElapsed);
             proto.write(AlarmManagerServiceDumpProto.LAST_TIME_CHANGE_CLOCK_TIME,
@@ -3918,7 +3960,7 @@
     void rescheduleKernelAlarmsLocked() {
         // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
         // prior to that which contains no wakeups, we schedule that as well.
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         long nextNonWakeup = 0;
         if (mAlarmStore.size() > 0) {
             final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
@@ -4031,7 +4073,7 @@
     @GuardedBy("mLock")
     private void removeAlarmsInternalLocked(Predicate<Alarm> whichAlarms, int reason) {
         final long nowRtc = mInjector.getCurrentTimeMillis();
-        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowElapsed = mInjector.getElapsedRealtimeMillis();
 
         final ArrayList<Alarm> removedAlarms = mAlarmStore.remove(whichAlarms);
         final boolean removedFromStore = !removedAlarms.isEmpty();
@@ -4170,7 +4212,7 @@
     void interactiveStateChangedLocked(boolean interactive) {
         if (mInteractive != interactive) {
             mInteractive = interactive;
-            final long nowELAPSED = mInjector.getElapsedRealtime();
+            final long nowELAPSED = mInjector.getElapsedRealtimeMillis();
             if (interactive) {
                 if (mPendingNonWakeupAlarms.size() > 0) {
                     final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime;
@@ -4272,7 +4314,15 @@
     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);
-    private static native int setKernelTime(long nativeData, long millis);
+
+    /*
+     * 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);
 
@@ -4310,7 +4360,7 @@
                     ent.pkg = alarm.sourcePackage;
                     ent.tag = alarm.statsTag;
                     ent.op = "END IDLE";
-                    ent.elapsedRealtime = mInjector.getElapsedRealtime();
+                    ent.elapsedRealtime = mInjector.getElapsedRealtimeMillis();
                     ent.argRealtime = alarm.getWhenElapsed();
                     mAllowWhileIdleDispatches.add(ent);
                 }
@@ -4541,24 +4591,41 @@
             return AlarmManagerService.getNextAlarm(mNativeData, type);
         }
 
-        void setKernelTimezone(int minutesWest) {
-            AlarmManagerService.setKernelTimezone(mNativeData, minutesWest);
+        void setKernelTimeZoneOffset(int utcOffsetMillis) {
+            // Kernel tracks time offsets as 'minutes west of GMT'
+            AlarmManagerService.setKernelTimezone(mNativeData, -(utcOffsetMillis / 60000));
         }
 
-        void setKernelTime(long millis) {
-            if (mNativeData != 0) {
-                AlarmManagerService.setKernelTime(mNativeData, millis);
-            }
+        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();
+        }
+
+        void setCurrentTimeMillis(
+                @CurrentTimeMillisLong long unixEpochMillis,
+                @TimeConfidence int confidence,
+                @NonNull String logMsg) {
+            SystemClockTime.setTimeAndConfidence(unixEpochMillis, confidence, logMsg);
         }
 
         void close() {
             AlarmManagerService.close(mNativeData);
         }
 
-        long getElapsedRealtime() {
+        @ElapsedRealtimeLong
+        long getElapsedRealtimeMillis() {
             return SystemClock.elapsedRealtime();
         }
 
+        @CurrentTimeMillisLong
         long getCurrentTimeMillis() {
             return System.currentTimeMillis();
         }
@@ -4608,7 +4675,7 @@
             while (true) {
                 int result = mInjector.waitForAlarm();
                 final long nowRTC = mInjector.getCurrentTimeMillis();
-                final long nowELAPSED = mInjector.getElapsedRealtime();
+                final long nowELAPSED = mInjector.getElapsedRealtimeMillis();
                 synchronized (mLock) {
                     mLastWakeup = nowELAPSED;
                 }
@@ -4856,7 +4923,7 @@
                     // this way, so WAKE_UP alarms will be delivered only when the device is awake.
                     ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
                     synchronized (mLock) {
-                        final long nowELAPSED = mInjector.getElapsedRealtime();
+                        final long nowELAPSED = mInjector.getElapsedRealtimeMillis();
                         triggerAlarmsLocked(triggerList, nowELAPSED);
                         updateNextAlarmClockLocked();
                     }
@@ -5009,13 +5076,12 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
-                // Since the kernel does not keep track of DST, we need to
-                // reset the TZ information at the beginning of each day
-                // based off of the current Zone gmt offset + userspace tracked
-                // daylight savings information.
-                TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY));
-                int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis());
-                mInjector.setKernelTimezone(-(gmtOffset / 60000));
+                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();
             }
         }
@@ -5034,7 +5100,7 @@
             flags |= mConstants.TIME_TICK_ALLOWED_WHILE_IDLE ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
                     : 0;
 
-            setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
+            setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtimeMillis() + tickEventDelay, 0,
                     0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
                     Process.myUid(), "android", null, EXACT_ALLOW_REASON_ALLOW_LIST);
 
@@ -5221,7 +5287,7 @@
             }
             synchronized (mLock) {
                 mTemporaryQuotaReserve.replenishQuota(packageName, userId, quotaBump,
-                        mInjector.getElapsedRealtime());
+                        mInjector.getElapsedRealtimeMillis());
             }
             mHandler.obtainMessage(AlarmHandler.TEMPORARY_QUOTA_CHANGED, userId, -1,
                     packageName).sendToTarget();
@@ -5383,7 +5449,7 @@
         }
 
         private void updateStatsLocked(InFlight inflight) {
-            final long nowELAPSED = mInjector.getElapsedRealtime();
+            final long nowELAPSED = mInjector.getElapsedRealtimeMillis();
             BroadcastStats bs = inflight.mBroadcastStats;
             bs.nesting--;
             if (bs.nesting <= 0) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4d5eef2..f5c0ed9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -116,6 +116,7 @@
 import com.android.server.job.restrictions.ThermalStatusRestriction;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.tare.EconomyManagerInternal;
+import com.android.server.tare.JobSchedulerEconomicPolicy;
 import com.android.server.usage.AppStandbyInternal;
 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 import com.android.server.utils.quota.Categorizer;
@@ -373,10 +374,12 @@
                     JobSchedulerBackgroundThread.getExecutor(), this);
             final EconomyManagerInternal economyManagerInternal =
                     LocalServices.getService(EconomyManagerInternal.class);
-            economyManagerInternal.registerTareStateChangeListener(this);
+            economyManagerInternal
+                    .registerTareStateChangeListener(this, JobSchedulerEconomicPolicy.POLICY_JOB);
             // Load all the constants.
             synchronized (mLock) {
-                mConstants.updateTareSettingsLocked(economyManagerInternal.isEnabled());
+                mConstants.updateTareSettingsLocked(
+                        economyManagerInternal.isEnabled(JobSchedulerEconomicPolicy.POLICY_JOB));
             }
             onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
         }
@@ -882,6 +885,8 @@
                                             // a user-initiated action, it should be fine to just
                                             // put USER instead of UNINSTALL or DISABLED.
                                             cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+                                                    /* includeSchedulingApp */ true,
+                                                    /* includeSourceApp */ true,
                                                     JobParameters.STOP_REASON_USER,
                                                     JobParameters.INTERNAL_STOP_REASON_UNINSTALL,
                                                     "app disabled");
@@ -932,6 +937,7 @@
                     // get here, but since this is generally a user-initiated action, it should
                     // be fine to just put USER instead of UNINSTALL or DISABLED.
                     cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+                            /* includeSchedulingApp */ true, /* includeSourceApp */ true,
                             JobParameters.STOP_REASON_USER,
                             JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "app uninstalled");
                     for (int c = 0; c < mControllers.size(); ++c) {
@@ -986,7 +992,12 @@
                         Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
                     }
                     synchronized (mLock) {
+                        // Exclude jobs scheduled on behalf of this app for now because SyncManager
+                        // and other job proxy agents may not know to reschedule the job properly
+                        // after force stop.
+                        // TODO(209852664): determine how to best handle syncs & other proxied jobs
                         cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+                                /* includeSchedulingApp */ true, /* includeSourceApp */ false,
                                 JobParameters.STOP_REASON_USER,
                                 JobParameters.INTERNAL_STOP_REASON_CANCELED,
                                 "app force stopped");
@@ -1304,16 +1315,18 @@
         }
     }
 
+    private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
+        // There's no guarantee that the process has been stopped by the time we get
+        // here, but since this is a user-initiated action, it should be fine to just
+        // put USER instead of UNINSTALL or DISABLED.
+        cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+                JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed");
+    };
+
     private void cancelJobsForUserLocked(int userHandle) {
-        final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
-        for (int i = 0; i < jobsForUser.size(); i++) {
-            JobStatus toRemove = jobsForUser.get(i);
-            // There's no guarantee that the process has been stopped by the time we get here,
-            // but since this is a user-initiated action, it should be fine to just put USER
-            // instead of UNINSTALL or DISABLED.
-            cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
-                    JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed");
-        }
+        mJobs.forEachJob(
+                (job) -> job.getUserId() == userHandle || job.getSourceUserId() == userHandle,
+                mCancelJobDueToUserRemovalConsumer);
     }
 
     private void cancelJobsForNonExistentUsers() {
@@ -1324,15 +1337,31 @@
     }
 
     private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
+            boolean includeSchedulingApp, boolean includeSourceApp,
             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+        if (!includeSchedulingApp && !includeSourceApp) {
+            Slog.wtfStack(TAG,
+                    "Didn't indicate whether to cancel jobs for scheduling and/or source app");
+            includeSourceApp = true;
+        }
         if ("android".equals(pkgName)) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
             return;
         }
-        final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
+        final List<JobStatus> jobsForUid = new ArrayList<>();
+        if (includeSchedulingApp) {
+            mJobs.getJobsByUid(uid, jobsForUid);
+        }
+        if (includeSourceApp) {
+            mJobs.getJobsBySourceUid(uid, jobsForUid);
+        }
         for (int i = jobsForUid.size() - 1; i >= 0; i--) {
             final JobStatus job = jobsForUid.get(i);
-            if (job.getSourcePackageName().equals(pkgName)) {
+            final boolean shouldCancel =
+                    (includeSchedulingApp
+                            && job.getServiceComponent().getPackageName().equals(pkgName))
+                    || (includeSourceApp && job.getSourcePackageName().equals(pkgName));
+            if (shouldCancel) {
                 cancelJobImplLocked(job, null, reason, internalReasonCode, debugReason);
             }
         }
@@ -1404,8 +1433,31 @@
         }
         mChangedJobList.remove(cancelled);
         // Cancel if running.
-        mConcurrencyManager.stopJobOnServiceContextLocked(
+        final boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
                 cancelled, reason, internalReasonCode, debugReason);
+        // If the job was running, the JobServiceContext should log with state FINISHED.
+        if (!wasRunning) {
+            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
+                    cancelled.getSourceUid(), null, cancelled.getBatteryName(),
+                    FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED,
+                    internalReasonCode, cancelled.getStandbyBucket(),
+                    cancelled.getJobId(),
+                    cancelled.hasChargingConstraint(),
+                    cancelled.hasBatteryNotLowConstraint(),
+                    cancelled.hasStorageNotLowConstraint(),
+                    cancelled.hasTimingDelayConstraint(),
+                    cancelled.hasDeadlineConstraint(),
+                    cancelled.hasIdleConstraint(),
+                    cancelled.hasConnectivityConstraint(),
+                    cancelled.hasContentTriggerConstraint(),
+                    cancelled.isRequestedExpeditedJob(),
+                    /* isRunningAsExpeditedJob */ false,
+                    reason,
+                    cancelled.getJob().isPrefetch(),
+                    cancelled.getJob().getPriority(),
+                    cancelled.getEffectivePriority(),
+                    cancelled.getNumFailures());
+        }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
             if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 90ce8bf..d6456f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -364,9 +364,12 @@
                     job.getJob().getPriority(),
                     job.getEffectivePriority(),
                     job.getNumFailures());
-            // Use the context's ID to distinguish traces since there'll only be one job running
-            // per context.
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getTag(), getId());
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                // Use the context's ID to distinguish traces since there'll only be one job
+                // running per context.
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
+                        job.getTag(), getId());
+            }
             try {
                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
             } catch (RemoteException e) {
@@ -1030,7 +1033,10 @@
                 completedJob.getJob().getPriority(),
                 completedJob.getEffectivePriority(),
                 completedJob.getNumFailures());
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getTag(), getId());
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
+                    completedJob.getTag(), getId());
+        }
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
                     internalStopReason);
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 dfa1442..ff4d26d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -22,6 +22,7 @@
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.JobSchedulerService.sSystemClock;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.content.ComponentName;
@@ -32,7 +33,6 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
@@ -287,26 +287,37 @@
     }
 
     /**
-     * @param userHandle User for whom we are querying the list of jobs.
-     * @return A list of all the jobs scheduled for the provided user. Never null.
+     * @param sourceUid Uid of the source app.
+     * @return A list of all the jobs scheduled for the source app. Never null.
      */
-    public List<JobStatus> getJobsByUser(int userHandle) {
-        return mJobSet.getJobsByUser(userHandle);
+    @NonNull
+    public List<JobStatus> getJobsBySourceUid(int sourceUid) {
+        return mJobSet.getJobsBySourceUid(sourceUid);
+    }
+
+    public void getJobsBySourceUid(int sourceUid, @NonNull List<JobStatus> insertInto) {
+        mJobSet.getJobsBySourceUid(sourceUid, insertInto);
     }
 
     /**
      * @param uid Uid of the requesting app.
      * @return All JobStatus objects for a given uid from the master list. Never null.
      */
+    @NonNull
     public List<JobStatus> getJobsByUid(int uid) {
         return mJobSet.getJobsByUid(uid);
     }
 
+    public void getJobsByUid(int uid, @NonNull List<JobStatus> insertInto) {
+        mJobSet.getJobsByUid(uid, insertInto);
+    }
+
     /**
      * @param uid Uid of the requesting app.
      * @param jobId Job id, specified at schedule-time.
      * @return the JobStatus that matches the provided uId and jobId, or null if none found.
      */
+    @Nullable
     public JobStatus getJobByUidAndJobId(int uid, int jobId) {
         return mJobSet.get(uid, jobId);
     }
@@ -742,6 +753,10 @@
                 }
             } catch (XmlPullParserException | IOException e) {
                 Slog.wtf(TAG, "Error jobstore xml.", e);
+            } catch (Exception e) {
+                // Crashing at this point would result in a boot loop, so live with a general
+                // Exception for system stability's sake.
+                Slog.wtf(TAG, "Unexpected exception", e);
             } finally {
                 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
                     mPersistInfo.countAllJobsLoaded = numJobs;
@@ -890,6 +905,9 @@
             } catch (IOException e) {
                 Slog.d(TAG, "Error I/O Exception.", e);
                 return null;
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Constraints contained invalid data", e);
+                return null;
             }
 
             parser.next(); // Consume </constraints>
@@ -986,8 +1004,14 @@
                 return null;
             }
 
-            PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
-            jobBuilder.setExtras(extras);
+            final PersistableBundle extras;
+            try {
+                extras = PersistableBundle.restoreFromXml(parser);
+                jobBuilder.setExtras(extras);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Persisted extras contained invalid data", e);
+                return null;
+            }
             parser.nextTag(); // Consume </extras>
 
             final JobInfo builtJob;
@@ -1071,6 +1095,8 @@
             }
 
             if ((netCapabilitiesIntArray != null) && (netTransportTypesIntArray != null)) {
+                // S+ format. No capability or transport validation since the values should be in
+                // line with what's defined in the Connectivity mainline module.
                 final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                         .clearCapabilities();
 
@@ -1087,6 +1113,7 @@
                 }
                 jobBuilder.setRequiredNetwork(builder.build());
             } else if (netCapabilitiesLong != null && netTransportTypesLong != null) {
+                // Format used on R- builds. Drop any unexpected capabilities and transports.
                 final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                         .clearCapabilities();
                 final int maxNetCapabilityInR = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
@@ -1207,27 +1234,31 @@
 
         public List<JobStatus> getJobsByUid(int uid) {
             ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            if (jobs != null) {
-                matchingJobs.addAll(jobs);
-            }
+            getJobsByUid(uid, matchingJobs);
             return matchingJobs;
         }
 
-        // By user, not by uid, so we need to traverse by key and check
-        public List<JobStatus> getJobsByUser(int userId) {
-            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
-            for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
-                if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
-                    final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
-                    if (jobs != null) {
-                        result.addAll(jobs);
-                    }
-                }
+        public void getJobsByUid(int uid, List<JobStatus> insertInto) {
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            if (jobs != null) {
+                insertInto.addAll(jobs);
             }
+        }
+
+        @NonNull
+        public List<JobStatus> getJobsBySourceUid(int sourceUid) {
+            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+            getJobsBySourceUid(sourceUid, result);
             return result;
         }
 
+        public void getJobsBySourceUid(int sourceUid, List<JobStatus> insertInto) {
+            final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
+            if (jobs != null) {
+                insertInto.addAll(jobs);
+            }
+        }
+
         public boolean add(JobStatus job) {
             final int uid = job.getUid();
             final int sourceUid = job.getSourceUid();
@@ -1369,7 +1400,7 @@
         }
 
         public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
-                Consumer<JobStatus> functor) {
+                @NonNull Consumer<JobStatus> functor) {
             for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
                 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
                 if (jobs != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 12ec9a4..7a13e3f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -1196,7 +1196,11 @@
                 final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
                 final EconomicPolicy.Action action = economicPolicy.getAction(aa.actionId);
                 if (action == null) {
-                    throw new IllegalArgumentException("Invalid action id: " + aa.actionId);
+                    if ((aa.actionId & EconomicPolicy.ALL_POLICIES) == 0) {
+                        throw new IllegalArgumentException("Invalid action id: " + aa.actionId);
+                    } else {
+                        Slog.w(TAG, "Tracking disabled policy's action? " + aa.actionId);
+                    }
                 }
             }
             mListener = listener;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index e791e98..b426f16 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -117,23 +117,23 @@
     private static final String TAG = "TARE- " + AlarmManagerEconomicPolicy.class.getSimpleName();
 
     public static final int ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE =
-            TYPE_ACTION | POLICY_AM | 0;
+            TYPE_ACTION | POLICY_ALARM | 0;
     public static final int ACTION_ALARM_WAKEUP_EXACT =
-            TYPE_ACTION | POLICY_AM | 1;
+            TYPE_ACTION | POLICY_ALARM | 1;
     public static final int ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE =
-            TYPE_ACTION | POLICY_AM | 2;
+            TYPE_ACTION | POLICY_ALARM | 2;
     public static final int ACTION_ALARM_WAKEUP_INEXACT =
-            TYPE_ACTION | POLICY_AM | 3;
+            TYPE_ACTION | POLICY_ALARM | 3;
     public static final int ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE =
-            TYPE_ACTION | POLICY_AM | 4;
+            TYPE_ACTION | POLICY_ALARM | 4;
     public static final int ACTION_ALARM_NONWAKEUP_EXACT =
-            TYPE_ACTION | POLICY_AM | 5;
+            TYPE_ACTION | POLICY_ALARM | 5;
     public static final int ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE =
-            TYPE_ACTION | POLICY_AM | 6;
+            TYPE_ACTION | POLICY_ALARM | 6;
     public static final int ACTION_ALARM_NONWAKEUP_INEXACT =
-            TYPE_ACTION | POLICY_AM | 7;
+            TYPE_ACTION | POLICY_ALARM | 7;
     public static final int ACTION_ALARM_CLOCK =
-            TYPE_ACTION | POLICY_AM | 8;
+            TYPE_ACTION | POLICY_ALARM | 8;
 
     private static final int[] COST_MODIFIERS = new int[]{
             COST_MODIFIER_CHARGING,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
index bc6fe7e5..f27da4a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -16,6 +16,8 @@
 
 package com.android.server.tare;
 
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
 import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
 import static com.android.server.tare.EconomicPolicy.TYPE_REGULATION;
 import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
@@ -23,9 +25,16 @@
 import static com.android.server.tare.TareUtils.cakeToString;
 
 import android.annotation.NonNull;
+import android.os.BatteryManagerInternal;
+import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,6 +47,8 @@
             || Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int NUM_PERIODS_TO_RETAIN = 8;
+    @VisibleForTesting
+    static final long MIN_REPORT_DURATION_FOR_RESET = 24 * HOUR_IN_MILLIS;
 
     static final class Report {
         /** How much the battery was discharged over the tracked period. */
@@ -73,6 +84,22 @@
         public long cumulativeNegativeRegulations = 0;
         public int numNegativeRegulations = 0;
 
+        /**
+         * The approximate amount of time the screen has been off while on battery while this
+         * report has been active.
+         */
+        public long screenOffDurationMs = 0;
+        /**
+         * The approximate amount of battery discharge while this report has been active.
+         */
+        public long screenOffDischargeMah = 0;
+        /** The offset used to get the delta when polling the screen off time from BatteryStats. */
+        private long bsScreenOffRealtimeBase = 0;
+        /**
+         * The offset used to get the delta when polling the screen off discharge from BatteryStats.
+         */
+        private long bsScreenOffDischargeMahBase = 0;
+
         private void clear() {
             cumulativeBatteryDischarge = 0;
             currentBatteryLevel = 0;
@@ -86,13 +113,27 @@
             numPositiveRegulations = 0;
             cumulativeNegativeRegulations = 0;
             numNegativeRegulations = 0;
+            screenOffDurationMs = 0;
+            screenOffDischargeMah = 0;
+            bsScreenOffRealtimeBase = 0;
+            bsScreenOffDischargeMahBase = 0;
         }
     }
 
+    private final IBatteryStats mIBatteryStats;
+
     private int mPeriodIndex = 0;
     /** How much the battery was discharged over the tracked period. */
     private final Report[] mReports = new Report[NUM_PERIODS_TO_RETAIN];
 
+    Analyst() {
+        this(BatteryStatsService.getService());
+    }
+
+    @VisibleForTesting Analyst(IBatteryStats iBatteryStats) {
+        mIBatteryStats = iBatteryStats;
+    }
+
     /** Returns the list of most recent reports, with the oldest report first. */
     @NonNull
     List<Report> getReports() {
@@ -107,13 +148,35 @@
         return list;
     }
 
+    long getBatteryScreenOffDischargeMah() {
+        long discharge = 0;
+        for (Report report : mReports) {
+            if (report == null) {
+                continue;
+            }
+            discharge += report.screenOffDischargeMah;
+        }
+        return discharge;
+    }
+
+    long getBatteryScreenOffDurationMs() {
+        long duration = 0;
+        for (Report report : mReports) {
+            if (report == null) {
+                continue;
+            }
+            duration += report.screenOffDurationMs;
+        }
+        return duration;
+    }
+
     /**
      * Tracks the given reports instead of whatever is currently saved. Reports should be ordered
      * oldest to most recent.
      */
     void loadReports(@NonNull List<Report> reports) {
         final int numReports = reports.size();
-        mPeriodIndex = Math.max(0, numReports - 1);
+        mPeriodIndex = Math.max(0, Math.min(NUM_PERIODS_TO_RETAIN, numReports) - 1);
         for (int i = 0; i < NUM_PERIODS_TO_RETAIN; ++i) {
             if (i < numReports) {
                 mReports[i] = reports.get(i);
@@ -121,22 +184,38 @@
                 mReports[i] = null;
             }
         }
+        final Report latest = mReports[mPeriodIndex];
+        if (latest != null) {
+            latest.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+            latest.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
+        }
     }
 
     void noteBatteryLevelChange(int newBatteryLevel) {
-        if (newBatteryLevel == 100 && mReports[mPeriodIndex] != null
-                && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel) {
+        final boolean deviceDischargedEnough = mReports[mPeriodIndex] != null
+                && newBatteryLevel >= 90
+                // Battery level is increasing, so device is charging.
+                && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel
+                && mReports[mPeriodIndex].cumulativeBatteryDischarge >= 25;
+        final boolean reportLongEnough = mReports[mPeriodIndex] != null
+                // Battery level is increasing, so device is charging.
+                && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel
+                && mReports[mPeriodIndex].screenOffDurationMs >= MIN_REPORT_DURATION_FOR_RESET;
+        final boolean shouldStartNewReport = deviceDischargedEnough || reportLongEnough;
+        if (shouldStartNewReport) {
             mPeriodIndex = (mPeriodIndex + 1) % NUM_PERIODS_TO_RETAIN;
             if (mReports[mPeriodIndex] != null) {
                 final Report report = mReports[mPeriodIndex];
                 report.clear();
                 report.currentBatteryLevel = newBatteryLevel;
+                report.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+                report.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
                 return;
             }
         }
 
         if (mReports[mPeriodIndex] == null) {
-            Report report = new Report();
+            Report report = initializeReport();
             mReports[mPeriodIndex] = report;
             report.currentBatteryLevel = newBatteryLevel;
             return;
@@ -145,13 +224,27 @@
         final Report report = mReports[mPeriodIndex];
         if (newBatteryLevel < report.currentBatteryLevel) {
             report.cumulativeBatteryDischarge += (report.currentBatteryLevel - newBatteryLevel);
+
+            final long latestScreenOffRealtime = getLatestBatteryScreenOffRealtimeMs();
+            final long latestScreenOffDischargeMah = getLatestScreenOffDischargeMah();
+            if (report.bsScreenOffRealtimeBase > latestScreenOffRealtime) {
+                // BatteryStats reset
+                report.bsScreenOffRealtimeBase = 0;
+                report.bsScreenOffDischargeMahBase = 0;
+            }
+            report.screenOffDurationMs +=
+                    (latestScreenOffRealtime - report.bsScreenOffRealtimeBase);
+            report.screenOffDischargeMah +=
+                    (latestScreenOffDischargeMah - report.bsScreenOffDischargeMahBase);
+            report.bsScreenOffRealtimeBase = latestScreenOffRealtime;
+            report.bsScreenOffDischargeMahBase = latestScreenOffDischargeMah;
         }
         report.currentBatteryLevel = newBatteryLevel;
     }
 
     void noteTransaction(@NonNull Ledger.Transaction transaction) {
         if (mReports[mPeriodIndex] == null) {
-            mReports[mPeriodIndex] = new Report();
+            mReports[mPeriodIndex] = initializeReport();
         }
         final Report report = mReports[mPeriodIndex];
         switch (getEventType(transaction.eventId)) {
@@ -191,6 +284,32 @@
         mPeriodIndex = 0;
     }
 
+    private long getLatestBatteryScreenOffRealtimeMs() {
+        try {
+            return mIBatteryStats.computeBatteryScreenOffRealtimeMs();
+        } catch (RemoteException e) {
+            // Shouldn't happen
+            return 0;
+        }
+    }
+
+    private long getLatestScreenOffDischargeMah() {
+        try {
+            return mIBatteryStats.getScreenOffDischargeMah();
+        } catch (RemoteException e) {
+            // Shouldn't happen
+            return 0;
+        }
+    }
+
+    @NonNull
+    private Report initializeReport() {
+        final Report report = new Report();
+        report.bsScreenOffRealtimeBase = getLatestBatteryScreenOffRealtimeMs();
+        report.bsScreenOffDischargeMahBase = getLatestScreenOffDischargeMah();
+        return report;
+    }
+
     @NonNull
     private String padStringWithSpaces(@NonNull String text, int targetLength) {
         // Make sure to have at least one space on either side.
@@ -199,6 +318,8 @@
     }
 
     void dump(IndentingPrintWriter pw) {
+        final BatteryManagerInternal bmi = LocalServices.getService(BatteryManagerInternal.class);
+        final long batteryCapacityMah = bmi.getBatteryFullCharge() / 1000;
         pw.println("Reports:");
         pw.increaseIndent();
         pw.print("      Total Discharge");
@@ -208,6 +329,7 @@
         pw.print(padStringWithSpaces("Rewards (avg/reward : avg/discharge)", statColsLength));
         pw.print(padStringWithSpaces("+Regs (avg/reg : avg/discharge)", statColsLength));
         pw.print(padStringWithSpaces("-Regs (avg/reg : avg/discharge)", statColsLength));
+        pw.print(padStringWithSpaces("Bg drain estimate", statColsLength));
         pw.println();
         for (int r = 0; r < NUM_PERIODS_TO_RETAIN; ++r) {
             final int idx = (mPeriodIndex - r + NUM_PERIODS_TO_RETAIN) % NUM_PERIODS_TO_RETAIN;
@@ -283,6 +405,15 @@
             } else {
                 pw.print(padStringWithSpaces("N/A", statColsLength));
             }
+            if (report.screenOffDurationMs > 0) {
+                pw.print(padStringWithSpaces(String.format("%d mAh (%.2f%%/hr)",
+                                report.screenOffDischargeMah,
+                                1.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
+                                        / (batteryCapacityMah * report.screenOffDurationMs)),
+                        statColsLength));
+            } else {
+                pw.print(padStringWithSpaces("N/A", statColsLength));
+            }
             pw.println();
         }
         pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 625f99d..66f7c35 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -18,24 +18,30 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.tare.EconomyManager;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import libcore.util.EmptyArray;
 
 /** Combines all enabled policies into one. */
 public class CompleteEconomicPolicy extends EconomicPolicy {
+    private static final String TAG = "TARE-" + CompleteEconomicPolicy.class.getSimpleName();
 
+    private final CompleteInjector mInjector;
     private final ArraySet<EconomicPolicy> mEnabledEconomicPolicies = new ArraySet<>();
     /** Lazily populated set of actions covered by this policy. */
     private final SparseArray<Action> mActions = new SparseArray<>();
     /** Lazily populated set of rewards covered by this policy. */
     private final SparseArray<Reward> mRewards = new SparseArray<>();
-    private final int[] mCostModifiers;
+    private int mEnabledEconomicPolicyIds = 0;
+    private int[] mCostModifiers = EmptyArray.INT;
     private long mInitialConsumptionLimit;
     private long mHardConsumptionLimit;
 
@@ -47,11 +53,34 @@
     CompleteEconomicPolicy(@NonNull InternalResourceService irs,
             @NonNull CompleteInjector injector) {
         super(irs);
-        if (injector.isPolicyEnabled(POLICY_AM)) {
-            mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(irs, injector));
+        mInjector = injector;
+
+        if (mInjector.isPolicyEnabled(POLICY_ALARM, null)) {
+            mEnabledEconomicPolicyIds |= POLICY_ALARM;
+            mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(mIrs, mInjector));
         }
-        if (injector.isPolicyEnabled(POLICY_JS)) {
-            mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(irs, injector));
+        if (mInjector.isPolicyEnabled(POLICY_JOB, null)) {
+            mEnabledEconomicPolicyIds |= POLICY_JOB;
+            mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(mIrs, mInjector));
+        }
+    }
+
+    @Override
+    void setup(@NonNull DeviceConfig.Properties properties) {
+        super.setup(properties);
+
+        mActions.clear();
+        mRewards.clear();
+
+        mEnabledEconomicPolicies.clear();
+        mEnabledEconomicPolicyIds = 0;
+        if (mInjector.isPolicyEnabled(POLICY_ALARM, properties)) {
+            mEnabledEconomicPolicyIds |= POLICY_ALARM;
+            mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(mIrs, mInjector));
+        }
+        if (mInjector.isPolicyEnabled(POLICY_JOB, properties)) {
+            mEnabledEconomicPolicyIds |= POLICY_JOB;
+            mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(mIrs, mInjector));
         }
 
         ArraySet<Integer> costModifiers = new ArraySet<>();
@@ -61,17 +90,8 @@
                 costModifiers.add(s);
             }
         }
-        mCostModifiers = new int[costModifiers.size()];
-        for (int i = 0; i < costModifiers.size(); ++i) {
-            mCostModifiers[i] = costModifiers.valueAt(i);
-        }
+        mCostModifiers = ArrayUtils.convertToIntArray(costModifiers);
 
-        updateLimits();
-    }
-
-    @Override
-    void setup(@NonNull DeviceConfig.Properties properties) {
-        super.setup(properties);
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
             mEnabledEconomicPolicies.valueAt(i).setup(properties);
         }
@@ -170,11 +190,37 @@
         return reward;
     }
 
+    boolean isPolicyEnabled(@Policy int policyId) {
+        return (mEnabledEconomicPolicyIds & policyId) == policyId;
+    }
+
+    int getEnabledPolicyIds() {
+        return mEnabledEconomicPolicyIds;
+    }
+
     @VisibleForTesting
     static class CompleteInjector extends Injector {
 
-        boolean isPolicyEnabled(int policy) {
-            return true;
+        boolean isPolicyEnabled(int policy, @Nullable DeviceConfig.Properties properties) {
+            final String key;
+            final boolean defaultEnable;
+            switch (policy) {
+                case POLICY_ALARM:
+                    key = EconomyManager.KEY_ENABLE_POLICY_ALARM;
+                    defaultEnable = EconomyManager.DEFAULT_ENABLE_POLICY_ALARM;
+                    break;
+                case POLICY_JOB:
+                    key = EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER;
+                    defaultEnable = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER;
+                    break;
+                default:
+                    Slog.wtf(TAG, "Unknown policy: " + policy);
+                    return false;
+            }
+            if (properties == null) {
+                return defaultEnable;
+            }
+            return properties.getBoolean(key, defaultEnable);
         }
     }
 
@@ -189,7 +235,10 @@
         pw.println("Cached actions:");
         pw.increaseIndent();
         for (int i = 0; i < mActions.size(); ++i) {
-            dumpAction(pw, mActions.valueAt(i));
+            final Action action = mActions.valueAt(i);
+            if (action != null) {
+                dumpAction(pw, action);
+            }
         }
         pw.decreaseIndent();
 
@@ -197,7 +246,10 @@
         pw.println("Cached rewards:");
         pw.increaseIndent();
         for (int i = 0; i < mRewards.size(); ++i) {
-            dumpReward(pw, mRewards.valueAt(i));
+            final Reward reward = mRewards.valueAt(i);
+            if (reward != null) {
+                dumpReward(pw, reward);
+            }
         }
         pw.decreaseIndent();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 7391bcf..008dcb8 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -57,9 +57,10 @@
 
     private static final int SHIFT_POLICY = 28;
     static final int MASK_POLICY = 0b11 << SHIFT_POLICY;
+    static final int ALL_POLICIES = MASK_POLICY;
     // Reserve 0 for the base/common policy.
-    static final int POLICY_AM = 1 << SHIFT_POLICY;
-    static final int POLICY_JS = 2 << SHIFT_POLICY;
+    public static final int POLICY_ALARM = 1 << SHIFT_POLICY;
+    public static final int POLICY_JOB = 2 << SHIFT_POLICY;
 
     static final int MASK_EVENT = -1 ^ (MASK_TYPE | MASK_POLICY);
 
@@ -115,6 +116,15 @@
     }
 
     @IntDef({
+            ALL_POLICIES,
+            POLICY_ALARM,
+            POLICY_JOB,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Policy {
+    }
+
+    @IntDef({
             REWARD_TOP_ACTIVITY,
             REWARD_NOTIFICATION_SEEN,
             REWARD_NOTIFICATION_INTERACTION,
@@ -342,7 +352,7 @@
     @NonNull
     static String actionToString(int eventId) {
         switch (eventId & MASK_POLICY) {
-            case POLICY_AM:
+            case POLICY_ALARM:
                 switch (eventId) {
                     case AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE:
                         return "ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE";
@@ -365,7 +375,7 @@
                 }
                 break;
 
-            case POLICY_JS:
+            case POLICY_JOB:
                 switch (eventId) {
                     case JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START:
                         return "JOB_MAX_START";
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
index 0fa0c47..716769c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java
@@ -138,6 +138,9 @@
     /** Returns true if TARE is enabled. */
     boolean isEnabled();
 
+    /** Returns true if TARE and the specified policy are enabled. */
+    boolean isEnabled(@EconomicPolicy.Policy int policyId);
+
     /**
      * Register an {@link AffordabilityChangeListener} to track when an app's ability to afford the
      * indicated bill changes.
@@ -155,7 +158,8 @@
     /**
      * Register a {@link TareStateChangeListener} to track when TARE's state changes.
      */
-    void registerTareStateChangeListener(@NonNull TareStateChangeListener listener);
+    void registerTareStateChangeListener(@NonNull TareStateChangeListener listener,
+            @EconomicPolicy.Policy int policyId);
 
     /**
      * Unregister a {@link TareStateChangeListener} from being notified when TARE's state changes.
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 c13e1dd9..dd0a194 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.tare.EconomyManager;
 import android.app.tare.IEconomyManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
@@ -81,7 +82,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.Objects;
 
 /**
  * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when
@@ -111,6 +112,19 @@
      * limit).
      */
     private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
+    /**
+     * The battery level above which we may consider adjusting the desired stock level.
+     */
+    private static final int STOCK_RECALCULATION_BATTERY_THRESHOLD = 80;
+    /**
+     * The amount of time to wait before considering recalculating the desired stock level.
+     */
+    private static final long STOCK_RECALCULATION_DELAY_MS = 16 * HOUR_IN_MILLIS;
+    /**
+     * The minimum amount of time we must have background drain for before considering
+     * recalculating the desired stock level.
+     */
+    private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS;
     private static final int PACKAGE_QUERY_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_APEX;
@@ -147,8 +161,9 @@
     @GuardedBy("mPackageToUidCache")
     private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
 
-    private final CopyOnWriteArraySet<TareStateChangeListener> mStateChangeListeners =
-            new CopyOnWriteArraySet<>();
+    @GuardedBy("mStateChangeListeners")
+    private final SparseSetArray<TareStateChangeListener> mStateChangeListeners =
+            new SparseSetArray<>();
 
     /**
      * List of packages that are fully restricted and shouldn't be allowed to run in the background.
@@ -164,6 +179,10 @@
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
 
+    /** Set of apps each installer is responsible for installing. */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
+
     private volatile boolean mHasBattery = true;
     private volatile boolean mIsEnabled;
     private volatile int mBootPhase;
@@ -172,6 +191,9 @@
     @GuardedBy("mLock")
     private int mCurrentBatteryLevel;
 
+    // TODO(250007395): make configurable per device
+    private final int mTargetBackgroundBatteryLifeHours;
+
     private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
         @Override
         public void opChanged(int op, int uid, String packageName) {
@@ -285,6 +307,7 @@
     private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
     private static final int MSG_PROCESS_USAGE_EVENT = 2;
     private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
+    private static final int MSG_NOTIFY_STATE_CHANGE_LISTENER = 4;
     private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
 
     /**
@@ -311,6 +334,11 @@
 
         mConfigObserver = new ConfigObserver(mHandler, context);
 
+        mTargetBackgroundBatteryLifeHours =
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+                        ? 200 // ~ 0.5%/hr
+                        : 100; // ~ 1%/hr
+
         publishLocalService(EconomyManagerInternal.class, new LocalService());
     }
 
@@ -353,6 +381,14 @@
         return mCompleteEconomicPolicy;
     }
 
+    /** Returns the number of apps that this app is expected to update at some point. */
+    int getAppUpdateResponsibilityCount(final int userId, @NonNull final String pkgName) {
+        synchronized (mLock) {
+            // TODO(248274798): return 0 if the app has lost the install permission
+            return ArrayUtils.size(mInstallers.get(userId, pkgName));
+        }
+    }
+
     @NonNull
     SparseArrayMap<String, InstalledPackageInfo> getInstalledPackages() {
         synchronized (mLock) {
@@ -408,6 +444,12 @@
         return mIsEnabled;
     }
 
+    boolean isEnabled(int policyId) {
+        synchronized (mLock) {
+            return isEnabled() && mCompleteEconomicPolicy.isPolicyEnabled(policyId);
+        }
+    }
+
     boolean isPackageExempted(final int userId, @NonNull String pkgName) {
         synchronized (mLock) {
             return mExemptedApps.contains(pkgName);
@@ -448,6 +490,9 @@
             mAnalyst.noteBatteryLevelChange(newBatteryLevel);
             final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
             if (increased) {
+                if (newBatteryLevel >= STOCK_RECALCULATION_BATTERY_THRESHOLD) {
+                    maybeAdjustDesiredStockLevelLocked();
+                }
                 mAgent.distributeBasicIncomeLocked(newBatteryLevel);
             } else if (newBatteryLevel == mCurrentBatteryLevel) {
                 // The broadcast is also sent when the plug type changes...
@@ -525,7 +570,8 @@
         }
         synchronized (mLock) {
             final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
-            mPkgCache.add(userId, pkgName, ipo);
+            final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
+            maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             mUidToPackageCache.add(uid, pkgName);
             // TODO: only do this when the user first launches the app (app leaves stopped state)
             mAgent.grantBirthrightLocked(userId, pkgName);
@@ -552,7 +598,14 @@
         synchronized (mLock) {
             mUidToPackageCache.remove(uid, pkgName);
             mVipOverrides.delete(userId, pkgName);
-            mPkgCache.delete(userId, pkgName);
+            final InstalledPackageInfo ipo = mPkgCache.delete(userId, pkgName);
+            mInstallers.delete(userId, pkgName);
+            if (ipo != null && ipo.installerPackageName != null) {
+                final ArraySet<String> list = mInstallers.get(userId, ipo.installerPackageName);
+                if (list != null) {
+                    list.remove(pkgName);
+                }
+            }
             mAgent.onPackageRemovedLocked(userId, pkgName);
         }
     }
@@ -574,7 +627,8 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
-                mPkgCache.add(userId, ipo.packageName, ipo);
+                final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+                maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
             mAgent.grantBirthrightsLocked(userId);
         }
@@ -590,6 +644,7 @@
                     mUidToPackageCache.remove(pkgInfo.uid);
                 }
             }
+            mInstallers.delete(userId);
             mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId);
         }
@@ -600,6 +655,10 @@
      */
     @GuardedBy("mLock")
     void maybePerformQuantitativeEasingLocked() {
+        if (mConfigObserver.ENABLE_TIP3) {
+            maybeAdjustDesiredStockLevelLocked();
+            return;
+        }
         // We don't need to increase the limit if the device runs out of consumable credits
         // when the battery is low.
         final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked();
@@ -620,6 +679,68 @@
         }
     }
 
+    /**
+     * Adjust the consumption limit based on historical data and the target battery drain.
+     */
+    @GuardedBy("mLock")
+    void maybeAdjustDesiredStockLevelLocked() {
+        if (!mConfigObserver.ENABLE_TIP3) {
+            return;
+        }
+        // Don't adjust the limit too often or while the battery is low.
+        final long now = getCurrentTimeMillis();
+        if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS
+                || mCurrentBatteryLevel <= STOCK_RECALCULATION_BATTERY_THRESHOLD) {
+            return;
+        }
+
+        // For now, use screen off battery drain as a proxy for background battery drain.
+        // TODO: get more accurate background battery drain numbers
+        final long totalScreenOffDurationMs = mAnalyst.getBatteryScreenOffDurationMs();
+        if (totalScreenOffDurationMs < STOCK_RECALCULATION_MIN_DATA_DURATION_MS) {
+            return;
+        }
+        final long totalDischargeMah = mAnalyst.getBatteryScreenOffDischargeMah();
+        final long batteryCapacityMah = mBatteryManagerInternal.getBatteryFullCharge() / 1000;
+        final long estimatedLifeHours = batteryCapacityMah * totalScreenOffDurationMs
+                / totalDischargeMah / HOUR_IN_MILLIS;
+        final long percentageOfTarget =
+                100 * estimatedLifeHours / mTargetBackgroundBatteryLifeHours;
+        if (DEBUG) {
+            Slog.d(TAG, "maybeAdjustDesiredStockLevelLocked:"
+                    + " screenOffMs=" + totalScreenOffDurationMs
+                    + " dischargeMah=" + totalDischargeMah
+                    + " capacityMah=" + batteryCapacityMah
+                    + " estimatedLifeHours=" + estimatedLifeHours
+                    + " %ofTarget=" + percentageOfTarget);
+        }
+        final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
+        final long newConsumptionLimit;
+        if (percentageOfTarget > 105) {
+            // The stock is too low. We're doing pretty well. We can increase the stock slightly
+            // to let apps do more work in the background.
+            newConsumptionLimit = Math.min((long) (currentConsumptionLimit * 1.01),
+                    mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+        } else if (percentageOfTarget < 100) {
+            // The stock is too high IMO. We're below the target. Decrease the stock to reduce
+            // background work.
+            newConsumptionLimit = Math.max((long) (currentConsumptionLimit * .98),
+                    mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+        } else {
+            // The stock is just right.
+            return;
+        }
+        // TODO(250007191): calculate and log implied service level
+        if (newConsumptionLimit != currentConsumptionLimit) {
+            Slog.i(TAG, "Adjusting consumption limit from " + cakeToString(currentConsumptionLimit)
+                    + " to " + cakeToString(newConsumptionLimit)
+                    + " because drain was " + percentageOfTarget + "% of target");
+            mScribe.setConsumptionLimitLocked(newConsumptionLimit);
+            adjustCreditSupplyLocked(/* allowIncrease */ true);
+            mScribe.setLastStockRecalculationTimeLocked(now);
+        }
+    }
+
     void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
             @NonNull Agent.ActionAffordabilityNote affordabilityNote) {
         if (DEBUG) {
@@ -746,11 +867,49 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
-                mPkgCache.add(userId, ipo.packageName, ipo);
+                final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+                maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
         }
     }
 
+    /**
+     * Used to update the set of installed apps for each installer. This only has an effect if the
+     * installer package name is different between {@code oldIpo} and {@code newIpo}.
+     */
+    @GuardedBy("mLock")
+    private void maybeUpdateInstallerStatusLocked(@Nullable InstalledPackageInfo oldIpo,
+            @NonNull InstalledPackageInfo newIpo) {
+        final boolean changed;
+        if (oldIpo == null) {
+            changed = newIpo.installerPackageName != null;
+        } else {
+            changed = !Objects.equals(oldIpo.installerPackageName, newIpo.installerPackageName);
+        }
+        if (!changed) {
+            return;
+        }
+        // InstallSourceInfo doesn't track userId, so for now, assume the installer on the package's
+        // user profile did the installation.
+        // TODO(246640162): use the actual installer's user ID
+        final int userId = UserHandle.getUserId(newIpo.uid);
+        final String pkgName = newIpo.packageName;
+        if (oldIpo != null) {
+            final ArraySet<String> oldList = mInstallers.get(userId, oldIpo.installerPackageName);
+            if (oldList != null) {
+                oldList.remove(pkgName);
+            }
+        }
+        if (newIpo.installerPackageName != null) {
+            ArraySet<String> newList = mInstallers.get(userId, newIpo.installerPackageName);
+            if (newList == null) {
+                newList = new ArraySet<>();
+                mInstallers.add(userId, newIpo.installerPackageName, newList);
+            }
+            newList.add(pkgName);
+        }
+    }
+
     private void registerListeners() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -933,9 +1092,30 @@
                 }
                 break;
 
+                case MSG_NOTIFY_STATE_CHANGE_LISTENER: {
+                    final int policy = msg.arg1;
+                    final TareStateChangeListener listener = (TareStateChangeListener) msg.obj;
+                    listener.onTareEnabledStateChanged(isEnabled(policy));
+                }
+                break;
+
                 case MSG_NOTIFY_STATE_CHANGE_LISTENERS: {
-                    for (TareStateChangeListener listener : mStateChangeListeners) {
-                        listener.onTareEnabledStateChanged(mIsEnabled);
+                    final int changedPolicies = msg.arg1;
+                    synchronized (mStateChangeListeners) {
+                        final int size = mStateChangeListeners.size();
+                        for (int l = 0; l < size; ++l) {
+                            final int policy = mStateChangeListeners.keyAt(l);
+                            if ((policy & changedPolicies) == 0) {
+                                continue;
+                            }
+                            final ArraySet<TareStateChangeListener> listeners =
+                                    mStateChangeListeners.get(policy);
+                            final boolean isEnabled = isEnabled(policy);
+                            for (int p = listeners.size() - 1; p >= 0; --p) {
+                                final TareStateChangeListener listener = listeners.valueAt(p);
+                                listener.onTareEnabledStateChanged(isEnabled);
+                            }
+                        }
                     }
                 }
                 break;
@@ -1040,16 +1220,28 @@
         }
 
         @Override
-        public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener) {
+        public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener,
+                int policyId) {
             if (!isTareSupported()) {
                 return;
             }
-            mStateChangeListeners.add(listener);
+            synchronized (mStateChangeListeners) {
+                if (mStateChangeListeners.add(policyId, listener)) {
+                    mHandler.obtainMessage(MSG_NOTIFY_STATE_CHANGE_LISTENER, policyId, 0, listener)
+                            .sendToTarget();
+                }
+            }
         }
 
         @Override
         public void unregisterTareStateChangeListener(@NonNull TareStateChangeListener listener) {
-            mStateChangeListeners.remove(listener);
+            synchronized (mStateChangeListeners) {
+                for (int i = mStateChangeListeners.size() - 1; i >= 0; --i) {
+                    final ArraySet<TareStateChangeListener> listeners =
+                            mStateChangeListeners.get(mStateChangeListeners.keyAt(i));
+                    listeners.remove(listener);
+                }
+            }
         }
 
         @Override
@@ -1114,6 +1306,11 @@
         }
 
         @Override
+        public boolean isEnabled(int policyId) {
+            return InternalResourceService.this.isEnabled(policyId);
+        }
+
+        @Override
         public void noteInstantaneousEvent(int userId, @NonNull String pkgName, int eventId,
                 @Nullable String tag) {
             if (!mIsEnabled) {
@@ -1152,7 +1349,12 @@
 
     private class ConfigObserver extends ContentObserver
             implements DeviceConfig.OnPropertiesChangedListener {
-        private static final String KEY_DC_ENABLE_TARE = "enable_tare";
+        private static final String KEY_ENABLE_TIP3 = "enable_tip3";
+
+        private static final boolean DEFAULT_ENABLE_TIP3 = true;
+
+        /** Use a target background battery drain rate to determine consumption limits. */
+        public boolean ENABLE_TIP3 = DEFAULT_ENABLE_TIP3;
 
         private final ContentResolver mContentResolver;
 
@@ -1200,12 +1402,16 @@
                         continue;
                     }
                     switch (name) {
-                        case KEY_DC_ENABLE_TARE:
+                        case EconomyManager.KEY_ENABLE_TARE:
                             updateEnabledStatus();
                             break;
+                        case KEY_ENABLE_TIP3:
+                            ENABLE_TIP3 = properties.getBoolean(name, DEFAULT_ENABLE_TIP3);
+                            break;
                         default:
                             if (!economicPolicyUpdated
-                                    && (name.startsWith("am") || name.startsWith("js"))) {
+                                    && (name.startsWith("am") || name.startsWith("js")
+                                    || name.startsWith("enable_policy"))) {
                                 updateEconomicPolicy();
                                 economicPolicyUpdated = true;
                             }
@@ -1217,7 +1423,7 @@
         private void updateEnabledStatus() {
             // User setting should override DeviceConfig setting.
             final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE,
-                    KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1);
+                    EconomyManager.KEY_ENABLE_TARE, EconomyManager.DEFAULT_ENABLE_TARE);
             final boolean isTareEnabled = isTareSupported()
                     && Settings.Global.getInt(mContentResolver,
                     Settings.Global.ENABLE_TARE, isTareEnabledDC ? 1 : 0) == 1;
@@ -1228,7 +1434,9 @@
                 } else {
                     tearDownEverything();
                 }
-                mHandler.sendEmptyMessage(MSG_NOTIFY_STATE_CHANGE_LISTENERS);
+                mHandler.obtainMessage(
+                                MSG_NOTIFY_STATE_CHANGE_LISTENERS, EconomicPolicy.ALL_POLICIES, 0)
+                        .sendToTarget();
             }
         }
 
@@ -1237,9 +1445,10 @@
                 final long initialLimit =
                         mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
                 final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
+                final int oldEnabledPolicies = mCompleteEconomicPolicy.getEnabledPolicyIds();
                 mCompleteEconomicPolicy.tearDown();
                 mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
-                if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) {
+                if (mIsEnabled && mBootPhase >= PHASE_THIRD_PARTY_APPS_CAN_START) {
                     mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties());
                     if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
                             || hardLimit
@@ -1249,6 +1458,13 @@
                                 mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
                     }
                     mAgent.onPricingChangedLocked();
+                    final int newEnabledPolicies = mCompleteEconomicPolicy.getEnabledPolicyIds();
+                    if (oldEnabledPolicies != newEnabledPolicies) {
+                        final int changedPolicies = oldEnabledPolicies ^ newEnabledPolicies;
+                        mHandler.obtainMessage(
+                                        MSG_NOTIFY_STATE_CHANGE_LISTENERS, changedPolicies, 0)
+                                .sendToTarget();
+                    }
                 }
             }
         }
@@ -1360,6 +1576,23 @@
             pw.println();
 
             pw.println();
+            pw.println("Installers:");
+            pw.increaseIndent();
+            for (int u = 0; u < mInstallers.numMaps(); ++u) {
+                final int userId = mInstallers.keyAt(u);
+
+                for (int p = 0; p < mInstallers.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mInstallers.keyAt(u, p);
+
+                    pw.print(appToString(userId, pkgName));
+                    pw.print(": ");
+                    pw.print(mInstallers.valueAt(u, p).size());
+                    pw.println(" apps");
+                }
+            }
+            pw.decreaseIndent();
+
+            pw.println();
             mCompleteEconomicPolicy.dump(pw);
 
             pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 55cc352..71c6d09 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -42,6 +42,7 @@
 import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES;
@@ -87,6 +88,7 @@
 import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
 import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT;
 import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_MAX;
@@ -131,19 +133,19 @@
 public class JobSchedulerEconomicPolicy extends EconomicPolicy {
     private static final String TAG = "TARE- " + JobSchedulerEconomicPolicy.class.getSimpleName();
 
-    public static final int ACTION_JOB_MAX_START = TYPE_ACTION | POLICY_JS | 0;
-    public static final int ACTION_JOB_MAX_RUNNING = TYPE_ACTION | POLICY_JS | 1;
-    public static final int ACTION_JOB_HIGH_START = TYPE_ACTION | POLICY_JS | 2;
-    public static final int ACTION_JOB_HIGH_RUNNING = TYPE_ACTION | POLICY_JS | 3;
-    public static final int ACTION_JOB_DEFAULT_START = TYPE_ACTION | POLICY_JS | 4;
-    public static final int ACTION_JOB_DEFAULT_RUNNING = TYPE_ACTION | POLICY_JS | 5;
-    public static final int ACTION_JOB_LOW_START = TYPE_ACTION | POLICY_JS | 6;
-    public static final int ACTION_JOB_LOW_RUNNING = TYPE_ACTION | POLICY_JS | 7;
-    public static final int ACTION_JOB_MIN_START = TYPE_ACTION | POLICY_JS | 8;
-    public static final int ACTION_JOB_MIN_RUNNING = TYPE_ACTION | POLICY_JS | 9;
-    public static final int ACTION_JOB_TIMEOUT = TYPE_ACTION | POLICY_JS | 10;
+    public static final int ACTION_JOB_MAX_START = TYPE_ACTION | POLICY_JOB | 0;
+    public static final int ACTION_JOB_MAX_RUNNING = TYPE_ACTION | POLICY_JOB | 1;
+    public static final int ACTION_JOB_HIGH_START = TYPE_ACTION | POLICY_JOB | 2;
+    public static final int ACTION_JOB_HIGH_RUNNING = TYPE_ACTION | POLICY_JOB | 3;
+    public static final int ACTION_JOB_DEFAULT_START = TYPE_ACTION | POLICY_JOB | 4;
+    public static final int ACTION_JOB_DEFAULT_RUNNING = TYPE_ACTION | POLICY_JOB | 5;
+    public static final int ACTION_JOB_LOW_START = TYPE_ACTION | POLICY_JOB | 6;
+    public static final int ACTION_JOB_LOW_RUNNING = TYPE_ACTION | POLICY_JOB | 7;
+    public static final int ACTION_JOB_MIN_START = TYPE_ACTION | POLICY_JOB | 8;
+    public static final int ACTION_JOB_MIN_RUNNING = TYPE_ACTION | POLICY_JOB | 9;
+    public static final int ACTION_JOB_TIMEOUT = TYPE_ACTION | POLICY_JOB | 10;
 
-    public static final int REWARD_APP_INSTALL = TYPE_REWARD | POLICY_JS | 0;
+    public static final int REWARD_APP_INSTALL = TYPE_REWARD | POLICY_JOB | 0;
 
     private static final int[] COST_MODIFIERS = new int[]{
             COST_MODIFIER_CHARGING,
@@ -154,6 +156,7 @@
 
     private long mMinSatiatedBalanceExempted;
     private long mMinSatiatedBalanceOther;
+    private long mMinSatiatedBalanceIncrementalAppUpdater;
     private long mMaxSatiatedBalance;
     private long mInitialSatiatedConsumptionLimit;
     private long mHardSatiatedConsumptionLimit;
@@ -183,11 +186,20 @@
         if (mIrs.isPackageRestricted(userId, pkgName)) {
             return 0;
         }
+
+        final long baseBalance;
         if (mIrs.isPackageExempted(userId, pkgName)) {
-            return mMinSatiatedBalanceExempted;
+            baseBalance = mMinSatiatedBalanceExempted;
+        } else {
+            baseBalance = mMinSatiatedBalanceOther;
         }
-        // TODO: take other exemptions into account
-        return mMinSatiatedBalanceOther;
+
+        long minBalance = baseBalance;
+
+        final int updateResponsibilityCount = mIrs.getAppUpdateResponsibilityCount(userId, pkgName);
+        minBalance += updateResponsibilityCount * mMinSatiatedBalanceIncrementalAppUpdater;
+
+        return Math.min(minBalance, mMaxSatiatedBalance);
     }
 
     @Override
@@ -242,6 +254,9 @@
         mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
             KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
             mMinSatiatedBalanceOther);
+        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
+                KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
         mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
             KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
@@ -397,10 +412,11 @@
 
     @Override
     void dump(IndentingPrintWriter pw) {
-        pw.println("Min satiated balances:");
+        pw.println("Min satiated balance:");
         pw.increaseIndent();
         pw.print("Exempted", cakeToString(mMinSatiatedBalanceExempted)).println();
         pw.print("Other", cakeToString(mMinSatiatedBalanceOther)).println();
+        pw.print("+App Updater", cakeToString(mMinSatiatedBalanceIncrementalAppUpdater)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
         pw.print("Consumption limits: [");
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 620d1a0d..a68170c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -286,7 +286,7 @@
             final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS)
                     % NUM_REWARD_BUCKET_WINDOWS;
             final RewardBucket rewardBucket = mRewardBuckets[idx];
-            if (rewardBucket == null) {
+            if (rewardBucket == null || rewardBucket.startTimeMs == 0) {
                 continue;
             }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md
index e338ed1..8d25ecc 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/README.md
+++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md
@@ -80,9 +80,9 @@
 Regulations are unique events invoked by the ~~government~~ system in order to get the whole economy
 moving smoothly.
 
-# Previous Implementations
+# Significant Changes
 
-## V0
+## Tare Improvement Proposal #1 (TIP1)
 
 The initial implementation/proposal combined the supply of resources with the allocation in a single
 mechanism. It defined the maximum number of resources (ARCs) available at a time, and then divided
@@ -98,10 +98,25 @@
 These problems effectively meant that misallocation was a big problem, demand wasn't well reflected,
 and some apps may not have been able to perform work even though they otherwise should have been.
 
-Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply (by the system) and
+TIP1 separated allocation (to apps) from supply (by the system) and
 allowed apps to accrue credits as appropriate while still limiting the total number of credits
 consumed.
 
+## Tare Improvement Proposal #3 (TIP3)
+
+TIP1 introduced Consumption Limits, which control the total number of ARCs that can be used to
+perform actions, based on the production costs of each action. The Consumption Limits were initially
+determined manually, but could increase in the system if apps used the full consumption limit before
+the device had drained to 50% battery. As with any system that relies on manually deciding
+parameters, the only mechanism to identify an optimal value is through experimentation, which can
+take many iterations and requires extended periods of time to observe results. The limits are also
+chosen and adjusted without consideration of the resulting battery drain of each possible value. In
+addition, having the system potentially increase the limit without considering a decrease introduced
+potential for battery life to get worse as time goes on and the user installed more background-work
+demanding apps.
+
+TIP3 uses a target background battery drain rate to dynamically adjust the Consumption Limit.
+
 # Potential Future Changes
 
 These are some ideas for further changes. There's no guarantee that they'll be implemented.
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 bd4fd72..27d00b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -84,6 +84,8 @@
     private static final String XML_ATTR_USER_ID = "userId";
     private static final String XML_ATTR_VERSION = "version";
     private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
+    private static final String XML_ATTR_LAST_STOCK_RECALCULATION_TIME =
+            "lastStockRecalculationTime";
     private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
     private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
     private static final String XML_ATTR_PR_DISCHARGE = "discharge";
@@ -98,6 +100,8 @@
     private static final String XML_ATTR_PR_NUM_POS_REGULATIONS = "numPosRegulations";
     private static final String XML_ATTR_PR_NEG_REGULATIONS = "negRegulations";
     private static final String XML_ATTR_PR_NUM_NEG_REGULATIONS = "numNegRegulations";
+    private static final String XML_ATTR_PR_SCREEN_OFF_DURATION_MS = "screenOffDurationMs";
+    private static final String XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH = "screenOffDischargeMah";
 
     /** Version of the file schema. */
     private static final int STATE_FILE_VERSION = 0;
@@ -111,6 +115,8 @@
     @GuardedBy("mIrs.getLock()")
     private long mLastReclamationTime;
     @GuardedBy("mIrs.getLock()")
+    private long mLastStockRecalculationTime;
+    @GuardedBy("mIrs.getLock()")
     private long mSatiatedConsumptionLimit;
     @GuardedBy("mIrs.getLock()")
     private long mRemainingConsumableCakes;
@@ -173,6 +179,11 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    long getLastStockRecalculationTimeLocked() {
+        return mLastStockRecalculationTime;
+    }
+
+    @GuardedBy("mIrs.getLock()")
     @NonNull
     Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) {
         Ledger ledger = mLedgers.get(userId, pkgName);
@@ -281,6 +292,8 @@
                     case XML_TAG_HIGH_LEVEL_STATE:
                         mLastReclamationTime =
                                 parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
+                        mLastStockRecalculationTime = parser.getAttributeLong(null,
+                                XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0);
                         mSatiatedConsumptionLimit =
                                 parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
                                         mIrs.getInitialSatiatedConsumptionLimitLocked());
@@ -337,6 +350,12 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    void setLastStockRecalculationTimeLocked(long time) {
+        mLastStockRecalculationTime = time;
+        postWrite();
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void tearDownLocked() {
         TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
         TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
@@ -504,7 +523,6 @@
         return earliestEndTime;
     }
 
-
     /**
      * @param parser Xml parser at the beginning of a {@link #XML_TAG_PERIOD_REPORT} tag. The next
      *               "parser.next()" call will take the parser into the body of the report tag.
@@ -531,6 +549,10 @@
                 parser.getAttributeLong(null, XML_ATTR_PR_NEG_REGULATIONS);
         report.numNegativeRegulations =
                 parser.getAttributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS);
+        report.screenOffDurationMs =
+                parser.getAttributeLong(null, XML_ATTR_PR_SCREEN_OFF_DURATION_MS, 0);
+        report.screenOffDischargeMah =
+                parser.getAttributeLong(null, XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH, 0);
 
         return report;
     }
@@ -606,6 +628,8 @@
 
                 out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
                 out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
+                out.attributeLong(null,
+                        XML_ATTR_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime);
                 out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
                 out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES,
                         mRemainingConsumableCakes);
@@ -718,6 +742,8 @@
         out.attributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS, report.numPositiveRegulations);
         out.attributeLong(null, XML_ATTR_PR_NEG_REGULATIONS, report.cumulativeNegativeRegulations);
         out.attributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS, report.numNegativeRegulations);
+        out.attributeLong(null, XML_ATTR_PR_SCREEN_OFF_DURATION_MS, report.screenOffDurationMs);
+        out.attributeLong(null, XML_ATTR_PR_SCREEN_OFF_DISCHARGE_MAH, report.screenOffDischargeMah);
         out.endTag(null, XML_TAG_PERIOD_REPORT);
     }
 
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 8f9e187..b2ed4d4 100644
--- a/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
+++ b/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
@@ -76,19 +76,17 @@
 class AlarmImpl
 {
 public:
-    AlarmImpl(const TimerFds &fds, int epollfd, const std::string &rtc_dev)
-          : fds{fds}, epollfd{epollfd}, rtc_dev{rtc_dev} {}
+    AlarmImpl(const TimerFds &fds, int epollfd)
+          : fds{fds}, epollfd{epollfd} {}
     ~AlarmImpl();
 
     int set(int type, struct timespec *ts);
-    int setTime(struct timeval *tv);
     int waitForAlarm();
     int getTime(int type, struct itimerspec *spec);
 
 private:
     const TimerFds fds;
     const int epollfd;
-    std::string rtc_dev;
 };
 
 AlarmImpl::~AlarmImpl()
@@ -131,43 +129,6 @@
     return timerfd_gettime(fds[type], spec);
 }
 
-int AlarmImpl::setTime(struct timeval *tv)
-{
-    if (settimeofday(tv, NULL) == -1) {
-        ALOGV("settimeofday() failed: %s", strerror(errno));
-        return -1;
-    }
-
-    android::base::unique_fd fd{open(rtc_dev.c_str(), O_RDWR)};
-    if (!fd.ok()) {
-        ALOGE("Unable to open %s: %s", rtc_dev.c_str(), strerror(errno));
-        return -1;
-    }
-
-    struct tm tm;
-    if (!gmtime_r(&tv->tv_sec, &tm)) {
-        ALOGV("gmtime_r() failed: %s", strerror(errno));
-        return -1;
-    }
-
-    struct rtc_time rtc = {};
-    rtc.tm_sec = tm.tm_sec;
-    rtc.tm_min = tm.tm_min;
-    rtc.tm_hour = tm.tm_hour;
-    rtc.tm_mday = tm.tm_mday;
-    rtc.tm_mon = tm.tm_mon;
-    rtc.tm_year = tm.tm_year;
-    rtc.tm_wday = tm.tm_wday;
-    rtc.tm_yday = tm.tm_yday;
-    rtc.tm_isdst = tm.tm_isdst;
-    if (ioctl(fd, RTC_SET_TIME, &rtc) == -1) {
-        ALOGV("RTC_SET_TIME ioctl failed: %s", strerror(errno));
-        return -1;
-    }
-
-    return 0;
-}
-
 int AlarmImpl::waitForAlarm()
 {
     epoll_event events[N_ANDROID_TIMERFDS];
@@ -198,28 +159,6 @@
     return result;
 }
 
-static jint android_server_alarm_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis)
-{
-    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
-
-    if (millis <= 0 || millis / 1000LL >= std::numeric_limits<time_t>::max()) {
-        return -1;
-    }
-
-    struct timeval tv;
-    tv.tv_sec = (millis / 1000LL);
-    tv.tv_usec = ((millis % 1000LL) * 1000LL);
-
-    ALOGD("Setting time of day to sec=%ld", tv.tv_sec);
-
-    int ret = impl->setTime(&tv);
-    if (ret < 0) {
-        ALOGW("Unable to set rtc to %ld: %s", tv.tv_sec, strerror(errno));
-        ret = -1;
-    }
-    return ret;
-}
-
 static jint android_server_alarm_AlarmManagerService_setKernelTimezone(JNIEnv*, jobject, jlong, jint minswest)
 {
     struct timezone tz;
@@ -287,19 +226,7 @@
         }
     }
 
-    // Find the wall clock RTC. We expect this always to be /dev/rtc0, but
-    // check the /dev/rtc symlink first so that legacy devices that don't use
-    // rtc0 can add a symlink rather than need to carry a local patch to this
-    // code.
-    //
-    // TODO: if you're reading this in a world where all devices are using the
-    // GKI, you can remove the readlink and just assume /dev/rtc0.
-    std::string dev_rtc;
-    if (!android::base::Readlink("/dev/rtc", &dev_rtc)) {
-        dev_rtc = "/dev/rtc0";
-    }
-
-    std::unique_ptr<AlarmImpl> alarm{new AlarmImpl(fds, epollfd, dev_rtc)};
+    std::unique_ptr<AlarmImpl> alarm{new AlarmImpl(fds, epollfd)};
 
     for (size_t i = 0; i < fds.size(); i++) {
         epoll_event event;
@@ -392,7 +319,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},
-    {"setKernelTime", "(JJ)I", (void*)android_server_alarm_AlarmManagerService_setKernelTime},
     {"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 29eaec6..5714014 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -97,7 +97,9 @@
         "framework-bluetooth",
         "framework-connectivity",
         "framework-connectivity-t",
+        "framework-federatedcompute",
         "framework-graphics",
+        "framework-healthconnect",
         "framework-media",
         "framework-mediaprovider",
         "framework-ondevicepersonalization",
@@ -114,6 +116,7 @@
     ],
     system_server_classpath: [
         "service-art",
+        "service-healthconnect",
         "service-media-s",
         "service-permission",
         "service-sdksandbox",
diff --git a/api/Android.mk b/api/Android.mk
new file mode 100644
index 0000000..ce5f995
--- /dev/null
+++ b/api/Android.mk
@@ -0,0 +1,2 @@
+.PHONY: checkapi
+checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat
diff --git a/boot/Android.bp b/boot/Android.bp
index bb84494..7e55b3b 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -68,6 +68,10 @@
             module: "com.android.conscrypt-bootclasspath-fragment",
         },
         {
+            apex: "com.android.federatedcompute",
+            module: "com.android.federatedcompute-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.healthconnect",
             module: "com.android.healthconnect-bootclasspath-fragment",
         },
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 814800b..8be8cda 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -492,28 +492,13 @@
 status_t BootAnimation::readyToRun() {
     mAssets.addDefaultAssets();
 
-    mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
-    if (mDisplayToken == nullptr)
+    const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+    if (ids.empty()) {
+        SLOGE("Failed to get ID for any displays\n");
         return NAME_NOT_FOUND;
+    }
 
-    DisplayMode displayMode;
-    const status_t error =
-            SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &displayMode);
-    if (error != NO_ERROR)
-        return error;
-
-    mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
-    mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
-    ui::Size resolution = displayMode.resolution;
-    resolution = limitSurfaceSize(resolution.width, resolution.height);
-    // create the native surface
-    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
-            resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565,
-            ISurfaceComposerClient::eOpaque);
-
-    SurfaceComposerClient::Transaction t;
-
-    // this guest property specifies multi-display IDs to show the boot animation
+    // this system property specifies multi-display IDs to show the boot animation
     // multiple ids can be set with comma (,) as separator, for example:
     // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
     Vector<PhysicalDisplayId> physicalDisplayIds;
@@ -540,9 +525,44 @@
                 stream.ignore();
         }
 
+        // the first specified display id is used to retrieve mDisplayToken
+        for (const auto id : physicalDisplayIds) {
+            if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
+                if (const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id)) {
+                    mDisplayToken = token;
+                    break;
+                }
+            }
+        }
+    }
+
+    // If the system property is not present or invalid, display 0 is used
+    if (mDisplayToken == nullptr) {
+        mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        if (mDisplayToken == nullptr) {
+            return NAME_NOT_FOUND;
+        }
+    }
+
+    DisplayMode displayMode;
+    const status_t error =
+            SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &displayMode);
+    if (error != NO_ERROR) {
+        return error;
+    }
+
+    mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
+    mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
+    ui::Size resolution = displayMode.resolution;
+    resolution = limitSurfaceSize(resolution.width, resolution.height);
+    // create the native surface
+    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
+            resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565,
+            ISurfaceComposerClient::eOpaque);
+
+    SurfaceComposerClient::Transaction t;
+    if (isValid) {
         // In the case of multi-display, boot animation shows on the specified displays
-        // in addition to the primary display
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         for (const auto id : physicalDisplayIds) {
             if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
                 if (const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id)) {
@@ -570,8 +590,9 @@
     eglQuerySurface(display, surface, EGL_WIDTH, &w);
     eglQuerySurface(display, surface, EGL_HEIGHT, &h);
 
-    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
+    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
         return NO_INIT;
+    }
 
     mDisplay = display;
     mContext = context;
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 18ec5a4..4f8faca 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -43,6 +43,7 @@
         "-modernize-return-braced-init-list",
         "-modernize-use-default-member-init",
         "-modernize-use-equals-default",
+        "-modernize-use-emplace",
         "-modernize-use-nodiscard",
         "-modernize-use-override",
         "-modernize-use-trailing-return-type",
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 320e147..4431164 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -23,6 +23,7 @@
 #include <cstring>
 #include <filesystem>
 #include <fstream>
+#include <limits>
 #include <memory>
 #include <ostream>
 #include <string>
@@ -301,28 +302,42 @@
   return ok();
 }
 
-Status Idmap2Service::acquireFabricatedOverlayIterator() {
+Status Idmap2Service::acquireFabricatedOverlayIterator(int32_t* _aidl_return) {
+  std::lock_guard l(frro_iter_mutex_);
   if (frro_iter_.has_value()) {
     LOG(WARNING) << "active ffro iterator was not previously released";
   }
   frro_iter_ = std::filesystem::directory_iterator(kIdmapCacheDir);
+  if (frro_iter_id_ == std::numeric_limits<int32_t>::max()) {
+    frro_iter_id_ = 0;
+  } else {
+    ++frro_iter_id_;
+  }
+  *_aidl_return = frro_iter_id_;
   return ok();
 }
 
-Status Idmap2Service::releaseFabricatedOverlayIterator() {
+Status Idmap2Service::releaseFabricatedOverlayIterator(int32_t iteratorId) {
+  std::lock_guard l(frro_iter_mutex_);
   if (!frro_iter_.has_value()) {
     LOG(WARNING) << "no active ffro iterator to release";
+  } else if (frro_iter_id_ != iteratorId) {
+    LOG(WARNING) << "incorrect iterator id in a call to release";
   } else {
-      frro_iter_ = std::nullopt;
+    frro_iter_.reset();
   }
   return ok();
 }
 
-Status Idmap2Service::nextFabricatedOverlayInfos(
+Status Idmap2Service::nextFabricatedOverlayInfos(int32_t iteratorId,
     std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+  std::lock_guard l(frro_iter_mutex_);
+
   constexpr size_t kMaxEntryCount = 100;
   if (!frro_iter_.has_value()) {
     return error("no active frro iterator");
+  } else if (frro_iter_id_ != iteratorId) {
+    return error("incorrect iterator id in a call to next");
   }
 
   size_t count = 0;
@@ -330,22 +345,22 @@
   auto entry_iter_end = end(*frro_iter_);
   for (; entry_iter != entry_iter_end && count < kMaxEntryCount; ++entry_iter) {
     auto& entry = *entry_iter;
-    if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path())) {
+    if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path().native())) {
       continue;
     }
 
-    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path().native());
     if (!overlay) {
       LOG(WARNING) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
       continue;
     }
 
-    const auto info = (*overlay)->GetManifestInfo();
+    auto info = (*overlay)->GetManifestInfo();
     os::FabricatedOverlayInfo out_info;
-    out_info.packageName = info.package_name;
-    out_info.overlayName = info.name;
-    out_info.targetPackageName = info.target_package;
-    out_info.targetOverlayable = info.target_name;
+    out_info.packageName = std::move(info.package_name);
+    out_info.overlayName = std::move(info.name);
+    out_info.targetPackageName = std::move(info.target_package);
+    out_info.targetOverlayable = std::move(info.target_name);
     out_info.path = entry.path();
     _aidl_return->emplace_back(std::move(out_info));
     count++;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index c61e4bc..cc8cc5f 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -26,7 +26,10 @@
 
 #include <filesystem>
 #include <memory>
+#include <mutex>
+#include <optional>
 #include <string>
+#include <variant>
 #include <vector>
 
 namespace android::os {
@@ -60,11 +63,11 @@
   binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
                                          bool* _aidl_return) override;
 
-  binder::Status acquireFabricatedOverlayIterator() override;
+  binder::Status acquireFabricatedOverlayIterator(int32_t* _aidl_return) override;
 
-  binder::Status releaseFabricatedOverlayIterator() override;
+  binder::Status releaseFabricatedOverlayIterator(int32_t iteratorId) override;
 
-  binder::Status nextFabricatedOverlayInfos(
+  binder::Status nextFabricatedOverlayInfos(int32_t iteratorId,
       std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
 
   binder::Status dumpIdmap(const std::string& overlay_path, std::string* _aidl_return) override;
@@ -74,7 +77,9 @@
   // be able to be recalculated if idmap2 dies and restarts.
   std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
 
+  int32_t frro_iter_id_ = 0;
   std::optional<std::filesystem::directory_iterator> frro_iter_;
+  std::mutex frro_iter_mutex_;
 
   template <typename T>
   using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
diff --git a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 0059cf2..2bbfba9 100644
--- a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -41,9 +41,9 @@
   @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
   boolean deleteFabricatedOverlay(@utf8InCpp String path);
 
-  void acquireFabricatedOverlayIterator();
-  void releaseFabricatedOverlayIterator();
-  List<FabricatedOverlayInfo> nextFabricatedOverlayInfos();
+  int acquireFabricatedOverlayIterator();
+  void releaseFabricatedOverlayIterator(int iteratorId);
+  List<FabricatedOverlayInfo> nextFabricatedOverlayInfos(int iteratorId);
 
   @utf8InCpp String dumpIdmap(@utf8InCpp String overlayApkPath);
 }
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 4efaeea..813dff1 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -77,8 +77,7 @@
     return false;
   }
   uint32_t padding_size = CalculatePadding(size);
-  std::string padding(padding_size, '\0');
-  if (!stream.read(padding.data(), padding_size)) {
+  if (padding_size != 0 && !stream.seekg(padding_size, std::ios_base::cur)) {
     return false;
   }
   *out = buf;
@@ -222,7 +221,7 @@
         || !Read32(stream, &entry_count)) {
       return nullptr;
     }
-    target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count));
+    target_inline_entries.emplace_back(target_entry, entry_offset, entry_count);
   }
 
   // Read the inline overlay resource values
@@ -241,7 +240,7 @@
         || !Read32(stream, &value.data_value)) {
       return nullptr;
     }
-    target_values.emplace_back(std::make_pair(config_index, value));
+    target_values.emplace_back(config_index, value);
   }
 
   // Read the configurations
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index e1b7829..5a7fcd5 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -46,7 +46,6 @@
 
 using ::android::base::StringPrintf;
 using ::android::util::ExecuteBinary;
-using ::testing::NotNull;
 
 namespace android::idmap2 {
 
@@ -95,8 +94,8 @@
                                "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
 
   struct stat st;
   ASSERT_EQ(stat(GetIdmapPath().c_str(), &st), 0);
@@ -122,33 +121,33 @@
                                "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
                           "dump",
                           "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
 
-  ASSERT_NE(result->stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::integer::int1,
+  ASSERT_NE(result.stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::integer::int1,
                                                  R::overlay::integer::int1)),
             std::string::npos)
-      << result->stdout_str;
-  ASSERT_NE(result->stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str1,
+      << result.stdout_str;
+  ASSERT_NE(result.stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str1,
                                                  R::overlay::string::str1)),
             std::string::npos)
-      << result->stdout_str;
-  ASSERT_NE(result->stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str3,
+      << result.stdout_str;
+  ASSERT_NE(result.stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str3,
                                                  R::overlay::string::str3)),
             std::string::npos)
-      << result->stdout_str;
-  ASSERT_NE(result->stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str4,
+      << result.stdout_str;
+  ASSERT_NE(result.stdout_str.find(StringPrintf("0x%08x -> 0x%08x", R::target::string::str4,
                                                  R::overlay::string::str4)),
             std::string::npos)
-      << result->stdout_str;
+      << result.stdout_str;
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -156,9 +155,9 @@
                           "--verbose",
                           "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
-  ASSERT_NE(result->stdout_str.find("00000000: 504d4449  magic"), std::string::npos);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
+  ASSERT_NE(result.stdout_str.find("00000000: 504d4449  magic"), std::string::npos);
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -166,8 +165,8 @@
                           "--verbose",
                           "--idmap-path", GetTestDataPath() + "/DOES-NOT-EXIST"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, EXIT_SUCCESS);
+  ASSERT_TRUE((bool)result);
+  ASSERT_NE(result.status, EXIT_SUCCESS);
 
   unlink(GetIdmapPath().c_str());
 }
@@ -183,8 +182,8 @@
                                "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                                "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -193,10 +192,10 @@
                           "--config", "",
                           "--resid", StringPrintf("0x%08x", R::target::string::str1)});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
-  ASSERT_NE(result->stdout_str.find("overlay-1"), std::string::npos);
-  ASSERT_EQ(result->stdout_str.find("overlay-1-sv"), std::string::npos);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
+  ASSERT_NE(result.stdout_str.find("overlay-1"), std::string::npos);
+  ASSERT_EQ(result.stdout_str.find("overlay-1-sv"), std::string::npos);
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -205,10 +204,10 @@
                           "--config", "",
                           "--resid", "test.target:string/str1"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
-  ASSERT_NE(result->stdout_str.find("overlay-1"), std::string::npos);
-  ASSERT_EQ(result->stdout_str.find("overlay-1-sv"), std::string::npos);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
+  ASSERT_NE(result.stdout_str.find("overlay-1"), std::string::npos);
+  ASSERT_EQ(result.stdout_str.find("overlay-1-sv"), std::string::npos);
 
   // clang-format off
   result = ExecuteBinary({"idmap2",
@@ -217,9 +216,9 @@
                           "--config", "sv",
                           "--resid", "test.target:string/str1"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr_str;
-  ASSERT_NE(result->stdout_str.find("overlay-1-sv"), std::string::npos);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, EXIT_SUCCESS) << result.stderr_str;
+  ASSERT_NE(result.stdout_str.find("overlay-1-sv"), std::string::npos);
 
   unlink(GetIdmapPath().c_str());
 }
@@ -234,8 +233,8 @@
   auto result = ExecuteBinary({"idmap2",
                                "create"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, EXIT_SUCCESS);
+  ASSERT_TRUE((bool)result);
+  ASSERT_NE(result.status, EXIT_SUCCESS);
 
   // missing argument to option
   // clang-format off
@@ -246,8 +245,8 @@
                           "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                           "--idmap-path"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, EXIT_SUCCESS);
+  ASSERT_TRUE((bool)result);
+  ASSERT_NE(result.status, EXIT_SUCCESS);
 
   // invalid target apk path
   // clang-format off
@@ -258,8 +257,8 @@
                           "--overlay-name", TestConstants::OVERLAY_NAME_DEFAULT,
                           "--idmap-path", GetIdmapPath()});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, EXIT_SUCCESS);
+  ASSERT_TRUE((bool)result);
+  ASSERT_NE(result.status, EXIT_SUCCESS);
 
   // unknown policy
   // clang-format off
@@ -271,8 +270,8 @@
                           "--idmap-path", GetIdmapPath(),
                           "--policy", "this-does-not-exist"});
   // clang-format on
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, EXIT_SUCCESS);
+  ASSERT_TRUE((bool)result);
+  ASSERT_NE(result.status, EXIT_SUCCESS);
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 45740e2..cdc0b8f 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -192,7 +192,7 @@
     // 0x100 string contents "test"
     0x74, 0x65, 0x73, 0x74};
 
-const unsigned int kIdmapRawDataLen = 0x104;
+constexpr unsigned int kIdmapRawDataLen = std::size(kIdmapRawData);
 const unsigned int kIdmapRawDataOffset = 0x54;
 const unsigned int kIdmapRawDataTargetCrc = 0x1234;
 const unsigned int kIdmapRawOverlayCrc = 0x5678;
diff --git a/core/api/current.txt b/core/api/current.txt
index 19713cf..27d5ac3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4426,6 +4426,7 @@
     method public void readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ActivityManager.MemoryInfo> CREATOR;
+    field public long advertisedMem;
     field public long availMem;
     field public boolean lowMemory;
     field public long threshold;
@@ -9014,6 +9015,7 @@
   public final class CompanionDeviceManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
+    method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
     method @Deprecated public void disassociate(@NonNull String);
     method public void disassociate(int);
@@ -9819,6 +9821,7 @@
     field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
     field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1
     field public static final int CONTEXT_RESTRICTED = 4; // 0x4
+    field public static final String CREDENTIAL_SERVICE = "credential";
     field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
     field public static final String DEVICE_POLICY_SERVICE = "device_policy";
     field public static final String DISPLAY_HASH_SERVICE = "display_hash";
@@ -9831,6 +9834,7 @@
     field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String GAME_SERVICE = "game";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
+    field public static final String HEALTHCONNECT_SERVICE = "healthconnect";
     field public static final String INPUT_METHOD_SERVICE = "input_method";
     field public static final String INPUT_SERVICE = "input";
     field public static final String IPSEC_SERVICE = "ipsec";
@@ -11612,6 +11616,25 @@
     field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
+  public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.graphics.Bitmap getIcon();
+    method @NonNull public String getLabel();
+    method @NonNull public android.icu.util.ULocale getLocale();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR;
+  }
+
+  public static final class PackageInstaller.PreapprovalDetails.Builder {
+    ctor public PackageInstaller.PreapprovalDetails.Builder();
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails build();
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(@NonNull android.graphics.Bitmap);
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull String);
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(@NonNull android.icu.util.ULocale);
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(@NonNull String);
+  }
+
   public static class PackageInstaller.Session implements java.io.Closeable {
     method public void abandon();
     method public void addChildSessionId(int);
@@ -11941,6 +11964,7 @@
     field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final String FEATURE_CONTROLS = "android.software.controls";
+    field public static final String FEATURE_CREDENTIALS = "android.software.credentials";
     field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
     field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -12857,6 +12881,82 @@
 
 }
 
+package android.credentials {
+
+  public final class CreateCredentialRequest implements android.os.Parcelable {
+    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
+  }
+
+  public final class CreateCredentialResponse implements android.os.Parcelable {
+    ctor public CreateCredentialResponse(@NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialResponse> CREATOR;
+  }
+
+  public final class Credential implements android.os.Parcelable {
+    ctor public Credential(@NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.Credential> CREATOR;
+  }
+
+  public final class CredentialManager {
+    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
+    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+  }
+
+  public class CredentialManagerException extends java.lang.Exception {
+    ctor public CredentialManagerException(int, @Nullable String);
+    ctor public CredentialManagerException(int, @Nullable String, @Nullable Throwable);
+    ctor public CredentialManagerException(int, @Nullable Throwable);
+    ctor public CredentialManagerException(int);
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public final int errorCode;
+  }
+
+  public final class GetCredentialOption implements android.os.Parcelable {
+    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
+  }
+
+  public final class GetCredentialRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
+  }
+
+  public static final class GetCredentialRequest.Builder {
+    ctor public GetCredentialRequest.Builder();
+    method @NonNull public android.credentials.GetCredentialRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+    method @NonNull public android.credentials.GetCredentialRequest build();
+    method @NonNull public android.credentials.GetCredentialRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+  }
+
+  public final class GetCredentialResponse implements android.os.Parcelable {
+    ctor public GetCredentialResponse(@NonNull android.credentials.Credential);
+    ctor public GetCredentialResponse();
+    method public int describeContents();
+    method @Nullable public android.credentials.Credential getCredential();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialResponse> CREATOR;
+  }
+
+}
+
 package android.database {
 
   public abstract class AbstractCursor implements android.database.CrossProcessCursor {
@@ -15684,6 +15784,7 @@
     method public static android.graphics.Typeface createFromFile(@Nullable String);
     method public static android.graphics.Typeface defaultFromStyle(int);
     method public int getStyle();
+    method @Nullable public final String getSystemFontFamilyName();
     method @IntRange(from=0, to=1000) public int getWeight();
     method public final boolean isBold();
     method public final boolean isItalic();
@@ -18643,8 +18744,10 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
     field public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1; // 0x1
-    field public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
+    field public static final int LIGHT_CAPABILITY_COLOR_RGB = 2; // 0x2
+    field @Deprecated public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
     field public static final int LIGHT_TYPE_INPUT = 10001; // 0x2711
+    field public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003; // 0x2713
     field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
     field public static final int LIGHT_TYPE_PLAYER_ID = 10002; // 0x2712
   }
@@ -19445,6 +19548,7 @@
     method public boolean isFullTracking();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementRequest> CREATOR;
+    field public static final int PASSIVE_INTERVAL = 2147483647; // 0x7fffffff
   }
 
   public static final class GnssMeasurementRequest.Builder {
@@ -20062,6 +20166,7 @@
     field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc
     field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc
     field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc
+    field public static final int CHANNEL_OUT_6POINT1 = 1276; // 0x4fc
     field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc
     field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc
     field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc
@@ -22432,6 +22537,7 @@
     field public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
     field public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+    field public static final String MIMETYPE_IMAGE_AVIF = "image/avif";
     field public static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
     field public static final String MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
@@ -41550,6 +41656,10 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+    field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
+    field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
+    field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
+    field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING = "premium_capability_purchase_url_string";
     field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
     field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
     field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -41582,6 +41692,7 @@
     field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
     field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
+    field public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY = "supported_premium_capabilities_int_array";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -43073,6 +43184,7 @@
     method @NonNull public int[] getThresholds();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalThresholdInfo> CREATOR;
+    field public static final int SIGNAL_MEASUREMENT_TYPE_ECNO = 9; // 0x9
     field public static final int SIGNAL_MEASUREMENT_TYPE_RSCP = 2; // 0x2
     field public static final int SIGNAL_MEASUREMENT_TYPE_RSRP = 3; // 0x3
     field public static final int SIGNAL_MEASUREMENT_TYPE_RSRQ = 4; // 0x4
@@ -43645,6 +43757,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isModemEnabledForSlot(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported();
     method public boolean isNetworkRoaming();
+    method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int);
     method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
     method public boolean isRttSupported();
     method public boolean isSmsCapable();
@@ -43653,6 +43766,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
+    method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public void purchasePremiumCapability(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void rebootModem();
     method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method public void registerTelephonyCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
@@ -43822,6 +43936,20 @@
     field public static final int PHONE_TYPE_GSM = 1; // 0x1
     field public static final int PHONE_TYPE_NONE = 0; // 0x0
     field public static final int PHONE_TYPE_SIP = 3; // 0x3
+    field public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1; // 0x1
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4; // 0x4
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
+    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 = 5; // 0x5
     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
@@ -45066,6 +45194,14 @@
     method public void getChars(int, int, char[], int);
   }
 
+  public class GraphemeClusterSegmentFinder extends android.text.SegmentFinder {
+    ctor public GraphemeClusterSegmentFinder(@NonNull CharSequence, @NonNull android.text.TextPaint);
+    method public int nextEndBoundary(@IntRange(from=0) int);
+    method public int nextStartBoundary(@IntRange(from=0) int);
+    method public int previousEndBoundary(@IntRange(from=0) int);
+    method public int previousStartBoundary(@IntRange(from=0) int);
+  }
+
   public class Html {
     method public static String escapeHtml(CharSequence);
     method @Deprecated public static android.text.Spanned fromHtml(String);
@@ -45170,6 +45306,7 @@
     method public final int getLineAscent(int);
     method public final int getLineBaseline(int);
     method public final int getLineBottom(int);
+    method public int getLineBottom(int, boolean);
     method public int getLineBounds(int, android.graphics.Rect);
     method public abstract boolean getLineContainsTab(int);
     method public abstract int getLineCount();
@@ -45194,6 +45331,7 @@
     method public final int getParagraphLeft(int);
     method public final int getParagraphRight(int);
     method public float getPrimaryHorizontal(int);
+    method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
     method public float getSecondaryHorizontal(int);
     method public void getSelectionPath(int, int, android.graphics.Path);
     method public final float getSpacingAdd();
@@ -45217,6 +45355,9 @@
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
     field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3
+    field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
+    field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
+    field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -45230,6 +45371,10 @@
   public static class Layout.Directions {
   }
 
+  @java.lang.FunctionalInterface public static interface Layout.TextInclusionStrategy {
+    method public boolean isSegmentInside(@NonNull android.graphics.RectF, @NonNull android.graphics.RectF);
+  }
+
   @Deprecated public abstract class LoginFilter implements android.text.InputFilter {
     method @Deprecated public CharSequence filter(CharSequence, int, int, android.text.Spanned, int, int);
     method @Deprecated public abstract boolean isAllowed(char);
@@ -45306,6 +45451,15 @@
     method public android.text.PrecomputedText.Params.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
   }
 
+  public abstract class SegmentFinder {
+    ctor public SegmentFinder();
+    method public abstract int nextEndBoundary(@IntRange(from=0) int);
+    method public abstract int nextStartBoundary(@IntRange(from=0) int);
+    method public abstract int previousEndBoundary(@IntRange(from=0) int);
+    method public abstract int previousStartBoundary(@IntRange(from=0) int);
+    field public static final int DONE = -1; // 0xffffffff
+  }
+
   public class Selection {
     method public static boolean extendDown(android.text.Spannable, android.text.Layout);
     method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -45591,6 +45745,14 @@
     method public void onTextChanged(CharSequence, int, int, int);
   }
 
+  public class WordSegmentFinder extends android.text.SegmentFinder {
+    ctor public WordSegmentFinder(@NonNull CharSequence, @NonNull java.util.Locale);
+    method public int nextEndBoundary(@IntRange(from=0) int);
+    method public int nextStartBoundary(@IntRange(from=0) int);
+    method public int previousEndBoundary(@IntRange(from=0) int);
+    method public int previousStartBoundary(@IntRange(from=0) int);
+  }
+
 }
 
 package android.text.format {
@@ -49231,6 +49393,7 @@
     field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1
     field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2
     field public static final int CLASSIFICATION_NONE = 0; // 0x0
+    field public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3; // 0x3
     field @NonNull public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR;
     field public static final int EDGE_BOTTOM = 2; // 0x2
     field public static final int EDGE_LEFT = 4; // 0x4
@@ -51855,10 +52018,11 @@
     method public void setSpeechStateChangeTypes(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+    field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
     field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
     field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
     field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
-    field public static final int CONTENT_CHANGE_TYPE_INVALID = 1024; // 0x400
+    field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
     field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
     field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -53291,6 +53455,7 @@
     method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public default boolean performSpellCheck();
+    method public default boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
     method public default boolean requestCursorUpdates(int, int);
@@ -53434,6 +53599,7 @@
     method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
     method @Deprecated public void setAdditionalInputMethodSubtypes(@NonNull String, @NonNull android.view.inputmethod.InputMethodSubtype[]);
     method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
+    method public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String, @NonNull int[]);
     method @Deprecated public void setInputMethod(android.os.IBinder, String);
     method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
     method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
@@ -53519,8 +53685,8 @@
 
   public final class InsertGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public android.graphics.PointF getInsertionPoint();
-    method @Nullable public String getTextToInsert();
+    method @NonNull public android.graphics.PointF getInsertionPoint();
+    method @NonNull public String getTextToInsert();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertGesture> CREATOR;
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcfab4..134b71a4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -167,6 +167,7 @@
     field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
     field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+    field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
     field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
     field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
@@ -2789,6 +2790,8 @@
 
   public final class VirtualDeviceManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+    field public static final int DEFAULT_DEVICE_ID = 0; // 0x0
+    field public static final int INVALID_DEVICE_ID = -1; // 0xffffffff
     field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
     field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
     field public static final int LAUNCH_SUCCESS = 0; // 0x0
@@ -2808,6 +2811,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @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 public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -3951,6 +3955,7 @@
   }
 
   public final class DisplayManager {
+    method @Nullable @RequiresPermission("android.permission.CAPTURE_VIDEO_OUTPUT") public static android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface);
     method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
     method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfigurationForDisplay(@NonNull String);
@@ -6672,15 +6677,6 @@
 
 }
 
-package android.media.projection {
-
-  public class MediaProjectionGlobal {
-    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface);
-    method @NonNull public static android.media.projection.MediaProjectionGlobal getInstance();
-  }
-
-}
-
 package android.media.session {
 
   public final class MediaSessionManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 887af1f..bcd099e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -121,6 +121,7 @@
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
     method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
     method public long getTotalRam();
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
@@ -196,6 +197,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
     method public static boolean supportsMultiWindow(android.content.Context);
     method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
+    method @RequiresPermission("android.permission.UPDATE_LOCK_TASK_PACKAGES") public void updateLockTaskPackages(@NonNull android.content.Context, @NonNull String[]);
     field public static final int DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440; // 0x1b8
     field public static final int INVALID_STACK_ID = -1; // 0xffffffff
   }
@@ -443,6 +445,7 @@
     method public void syncInputTransactions();
     method public void syncInputTransactions(boolean);
     field @NonNull public static final java.util.Set<java.lang.String> ALL_PERMISSIONS;
+    field public static final int FLAG_NOT_ACCESSIBILITY_TOOL = 4; // 0x4
   }
 
   public class UiModeManager {
@@ -1168,9 +1171,11 @@
 package android.hardware.devicestate {
 
   public final class DeviceStateManager {
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride();
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
     method @NonNull public int[] getSupportedStates();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
     field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
@@ -1221,6 +1226,7 @@
     method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
     method @NonNull public int[] getUserDisabledHdrTypes();
     method public boolean isMinimalPostProcessingRequested(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
     method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
@@ -2477,6 +2483,11 @@
     ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
   }
 
+  public static class VoiceInteractionSession.ActivityId {
+    method @NonNull public android.os.IBinder getAssistToken();
+    method public int getTaskId();
+  }
+
 }
 
 package android.service.watchdog {
@@ -2883,7 +2894,6 @@
     ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
     method @NonNull public static android.os.IBinder getInternalDisplayToken();
     method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
-    method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]);
   }
 
   public class SurfaceControlViewHost {
@@ -3432,6 +3442,7 @@
     method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
     method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
     method public int describeContents();
+    method @NonNull public android.window.WindowContainerTransaction finishActivity(@NonNull android.os.IBinder);
     method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken);
     method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8f6bfd3..0a99c36 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,7 +784,8 @@
         mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
-        mIsAccessibilityTool = other.mIsAccessibilityTool;
+        // NOTE: Ensure that only properties that are safe to be modified by the service itself
+        // are included here (regardless of hidden setters, etc.).
     }
 
     private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS
index a31cfae..fb06e23 100644
--- a/core/java/android/accessibilityservice/OWNERS
+++ b/core/java/android/accessibilityservice/OWNERS
@@ -1,4 +1,6 @@
-svetoslavganov@google.com
 pweaver@google.com
-rhedjao@google.com
 ryanlwlin@google.com
+danielnorman@google.com
+sallyyuen@google.com
+aarmaly@google.com
+fuego@google.com
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 212e358..32d0d75 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -87,11 +87,13 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -155,6 +157,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ToolbarActionBar;
 import com.android.internal.app.WindowDecorActionBar;
@@ -1602,6 +1605,25 @@
         return callbacks;
     }
 
+    private void notifyVoiceInteractionManagerServiceActivityEvent(
+            @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+
+        final IVoiceInteractionManagerService service =
+                IVoiceInteractionManagerService.Stub.asInterface(
+                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        if (service == null) {
+            Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+                    + "VoiceInteractionManagerService");
+            return;
+        }
+
+        try {
+            service.notifyActivityEventChanged(mToken, type);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
     /**
      * Called when the activity is starting.  This is where most initialization
      * should go: calling {@link #setContentView(int)} to inflate the
@@ -1877,6 +1899,9 @@
         mCalled = true;
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
     }
 
     /**
@@ -2020,6 +2045,12 @@
         final Window win = getWindow();
         if (win != null) win.makeActive();
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+        // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+        // allow any binder call in onResume, we call this method in onPostResume.
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
         mCalled = true;
     }
 
@@ -2395,6 +2426,10 @@
         getAutofillClientController().onActivityPaused();
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
         mCalled = true;
     }
 
@@ -2624,6 +2659,9 @@
 
         getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
         mEnterAnimationComplete = false;
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
     }
 
     /**
@@ -8051,8 +8089,9 @@
                 resultData.prepareToLeaveProcess(this);
             }
             upIntent.prepareToLeaveProcess(this);
-            return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resultCode,
-                    resultData);
+            String resolvedType = upIntent.resolveTypeIfNeeded(getContentResolver());
+            return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resolvedType,
+                    resultCode, resultData);
         } else {
             return mParent.navigateUpToFromChild(this, upIntent);
         }
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 482f456..d1e6780 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -141,11 +141,11 @@
         }
     }
 
-    boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+    boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType, int resultCode,
             Intent resultData) {
         try {
-            return getActivityClientController().navigateUpTo(token, destIntent, resultCode,
-                    resultData);
+            return getActivityClientController().navigateUpTo(token, destIntent, resolvedType,
+                    resultCode, resultData);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index dfef279..576b572 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -29,6 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -2812,6 +2813,15 @@
      */
     public static class MemoryInfo implements Parcelable {
         /**
+         * The advertised memory of the system, as the end user would encounter in a retail display
+         * environment. This value might be different from {@code totalMem}. This could be due to
+         * many reasons. For example, the ODM could reserve part of the memory for the Trusted
+         * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it.
+         */
+        @SuppressLint("MutableBareField")
+        public long advertisedMem;
+
+        /**
          * The available memory on the system.  This number should not
          * be considered absolute: due to the nature of the kernel, a significant
          * portion of this memory is actually in use and needed for the overall
@@ -2860,6 +2870,7 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(advertisedMem);
             dest.writeLong(availMem);
             dest.writeLong(totalMem);
             dest.writeLong(threshold);
@@ -2871,6 +2882,7 @@
         }
 
         public void readFromParcel(Parcel source) {
+            advertisedMem = source.readLong();
             availMem = source.readLong();
             totalMem = source.readLong();
             threshold = source.readLong();
@@ -4375,13 +4387,15 @@
      * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
      * does not exist.
      *
-     * @param displayId id of the display, it must exist.
+     * @param displayId id of the display.
      *
      * @return whether the operation succeeded. Notice that if the user was already started in such
      * display before, it will return {@code false}.
      *
      * @throws UnsupportedOperationException if the device does not support background users on
      * secondary displays.
+     * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to
+     * start secondary users on.
      *
      * @hide
      */
@@ -4402,6 +4416,24 @@
     }
 
     /**
+     * Gets the id of displays that can be used by
+     * {@link #startUserInBackgroundOnSecondaryDisplay(int, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
+    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        try {
+            return getService().getSecondaryDisplayIdsForStartingBackgroundUsers();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the message that is shown when a user is switched from.
      *
      * @hide
@@ -4667,7 +4699,7 @@
      * @hide
      */
     public static void broadcastStickyIntent(Intent intent, int userId) {
-        broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
+        broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId);
     }
 
     /**
@@ -4676,11 +4708,20 @@
      * @hide
      */
     public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
+        broadcastStickyIntent(intent, appOp, null, userId);
+    }
+
+    /**
+     * Convenience for sending a sticky broadcast.  For internal use only.
+     *
+     * @hide
+     */
+    public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) {
         try {
             getService().broadcastIntentWithFeature(
                     null, null, intent, null, null, Activity.RESULT_OK, null, null,
                     null /*requiredPermissions*/, null /*excludedPermissions*/,
-                    null /*excludedPackages*/, appOp, null, false, true, userId);
+                    null /*excludedPackages*/, appOp, options, false, true, userId);
         } catch (RemoteException ex) {
         }
     }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 419b8e1..7d19ed4 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,8 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PermissionMethod;
+import android.content.pm.PermissionName;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -292,7 +294,8 @@
             boolean allowAll, int allowMode, String name, String callerPackage);
 
     /** Checks if the calling binder pid as the permission. */
-    public abstract void enforceCallingPermission(String permission, String func);
+    @PermissionMethod
+    public abstract void enforceCallingPermission(@PermissionName String permission, String func);
 
     /** Returns the current user id. */
     public abstract int getCurrentUserId();
@@ -744,10 +747,9 @@
      */
     public interface VoiceInteractionManagerProvider {
         /**
-         * Notifies the service when a high-level activity event has been changed, for example,
-         * an activity was resumed or stopped.
+         * Notifies the service when an activity is destroyed.
          */
-        void notifyActivityEventChanged();
+        void notifyActivityDestroyed(IBinder activityToken);
     }
 
     /**
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index f17d5b7..7cfca97 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -496,6 +496,16 @@
         }
     }
 
+    /** Update the list of packages allowed in lock task mode. */
+    @RequiresPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES)
+    public void updateLockTaskPackages(@NonNull Context context, @NonNull String[] packages) {
+        try {
+            getService().updateLockTaskPackages(context.getUserId(), packages);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Information you can retrieve about a root task in the system.
      * @hide
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf5f10b..7a9f3c1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6662,6 +6662,9 @@
 
         // Pass the current context to HardwareRenderer
         HardwareRenderer.setContextForInit(getSystemContext());
+        if (data.persistent) {
+            HardwareRenderer.setIsSystemOrPersistent();
+        }
 
         // Instrumentation info affects the class loader, so load it before
         // setting up the app context.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 03c1e07..28404d5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1910,7 +1910,6 @@
             OP_SCHEDULE_EXACT_ALARM,
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
-            OP_GET_USAGE_STATS,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index aa5fa5b..c2df802 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,12 +27,15 @@
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerExemptionManager.TempAllowListType;
 
+import java.util.Objects;
+
 /**
  * Helper class for building an options Bundle that can be used with
  * {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -55,6 +58,7 @@
     private boolean mRequireCompatChangeEnabled = true;
     private boolean mIsAlarmBroadcast = false;
     private long mIdForResponseEvent;
+    private @Nullable IntentFilter mRemoveMatchingFilter;
 
     /**
      * Change ID which is invalid.
@@ -180,11 +184,25 @@
     private static final String KEY_ID_FOR_RESPONSE_EVENT =
             "android:broadcast.idForResponseEvent";
 
+    /**
+     * Corresponds to {@link #setRemoveMatchingFilter}.
+     */
+    private static final String KEY_REMOVE_MATCHING_FILTER =
+            "android:broadcast.removeMatchingFilter";
+
     public static BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
         return opts;
     }
 
+    /** {@hide} */
+    public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
+            @NonNull IntentFilter removeMatchingFilter) {
+        BroadcastOptions opts = new BroadcastOptions();
+        opts.setRemoveMatchingFilter(removeMatchingFilter);
+        return opts;
+    }
+
     private BroadcastOptions() {
         super();
         resetTemporaryAppAllowlist();
@@ -216,6 +234,8 @@
         mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+        mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
+                IntentFilter.class);
     }
 
     /**
@@ -596,6 +616,29 @@
     }
 
     /**
+     * When enqueuing this broadcast, remove all pending broadcasts previously
+     * sent by this app which match the given filter.
+     * <p>
+     * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
+     * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
+     *
+     * @hide
+     */
+    public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
+        mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
+    }
+
+    /** @hide */
+    public void clearRemoveMatchingFilter() {
+        mRemoveMatchingFilter = null;
+    }
+
+    /** @hide */
+    public @Nullable IntentFilter getRemoveMatchingFilter() {
+        return mRemoveMatchingFilter;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -640,6 +683,9 @@
         if (mIdForResponseEvent != 0) {
             b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
         }
+        if (mRemoveMatchingFilter != null) {
+            b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 80121b7..0e1b47f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1094,7 +1094,7 @@
                 && (options == null
                         || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
             throw new AndroidRuntimeException(
-                    "Calling startActivity() from outside of an Activity "
+                    "Calling startActivity() from outside of an Activity"
                             + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                             + " Is this really what you want?");
         }
@@ -1128,7 +1128,7 @@
     public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
-                    "Calling startActivities() from outside of an Activity "
+                    "Calling startActivities() from outside of an Activity"
                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                     + " Is this really what you want?");
         }
@@ -1142,7 +1142,7 @@
         warnIfCallingFromSystemProcess();
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
-                    "Calling startActivities() from outside of an Activity "
+                    "Calling startActivities() from outside of an Activity"
                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                     + " Is this really what you want?");
         }
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index f5e5cda..9aa67bc 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -60,8 +60,8 @@
             in SizeConfigurationBuckets sizeConfigurations);
     boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
     boolean shouldUpRecreateTask(in IBinder token, in String destAffinity);
-    boolean navigateUpTo(in IBinder token, in Intent target, int resultCode,
-            in Intent resultData);
+    boolean navigateUpTo(in IBinder token, in Intent target, in String resolvedType,
+            int resultCode, in Intent resultData);
     boolean releaseActivityInstance(in IBinder token);
     boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask);
     boolean finishActivityAffinity(in IBinder token);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 980b79b..6404a1f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -524,9 +524,30 @@
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void suppressResizeConfigChanges(boolean suppress);
+
+    /**
+     * @deprecated Use {@link #unlockUser2(int, IProgressListener)} instead, since the token and
+     * secret arguments no longer do anything.  This method still exists only because it is marked
+     * with {@code @UnsupportedAppUsage}, so it might not be safe to remove it or change its
+     * signature.
+     */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean unlockUser(int userid, in byte[] token, in byte[] secret,
             in IProgressListener listener);
+
+    /**
+     * Tries to unlock the given user.
+     * <p>
+     * This will succeed only if the user's CE storage key is already unlocked or if the user
+     * doesn't have a lockscreen credential set.
+     *
+     * @param userId The ID of the user to unlock.
+     * @param listener An optional progress listener.
+     *
+     * @return true if the user was successfully unlocked, otherwise false.
+     */
+    boolean unlockUser2(int userId, in IProgressListener listener);
+
     void killPackageDependents(in String packageName, int userId);
     void makePackageIdle(String packageName, int userId);
     int getMemoryTrimLevel();
@@ -770,4 +791,10 @@
             "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
     boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
 
+    /**
+     * Gets the ids of displays that can be used on {@link #startUserInBackgroundOnSecondaryDisplay(int userId, int displayId)}.
+     *
+     * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+     */
+    @nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 6576a1a..91add27 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -73,6 +73,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
+import android.window.BackAnimationAdapter;
 import android.window.BackNavigationInfo;
 import android.window.SplashScreenView;
 import com.android.internal.app.IVoiceInteractor;
@@ -173,6 +174,7 @@
     Rect getTaskBounds(int taskId);
 
     void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES)")
     void updateLockTaskPackages(int userId, in String[] packages);
     boolean isInLockTaskMode();
     int getLockTaskModeState();
@@ -352,9 +354,10 @@
     /**
      * Prepare the back navigation in the server. This setups the leashed for sysui to animate
      * the back gesture and returns the data needed for the animation.
-     * @param requestAnimation true if the caller wishes to animate the back navigation
      * @param focusObserver a remote callback to nofify shell when the focused window lost focus.
+     * @param adaptor a remote animation to be run for the back navigation plays the animation.
+     * @return Returns the back navigation info.
      */
-    android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation,
-            in IWindowFocusObserver focusObserver);
+    android.window.BackNavigationInfo startBackNavigation(
+            in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 556058b..70d8a5e 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -390,7 +390,7 @@
     public void setInTouchMode(boolean inTouch) {
         try {
             IWindowManager.Stub.asInterface(
-                    ServiceManager.getService("window")).setInTouchMode(inTouch);
+                    ServiceManager.getService("window")).setInTouchModeOnAllDisplays(inTouch);
         } catch (RemoteException e) {
             // Shouldn't happen!
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9bbebc8..5695874 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4452,6 +4452,10 @@
          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
          * use full screen intents.</p>
+         * <p>
+         * To be launched as a full screen intent, the notification must also be posted to a
+         * channel with importance level set to IMPORTANCE_HIGH or higher.
+         * </p>
          *
          * @param intent The pending intent to launch.
          * @param highPriority Passing true will cause this notification to be sent
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 068304d..f3fc468 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -29,7 +29,7 @@
 per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
 per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
-per-file UiAutomation.java = file:/services/accessibility/OWNERS
+per-file UiAutomation* = file:/services/accessibility/OWNERS
 per-file GameManager* = file:/GAME_MANAGER_OWNERS
 per-file GameState* = file:/GAME_MANAGER_OWNERS
 per-file IGameManager* = file:/GAME_MANAGER_OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4da957c..4ddfdb6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -29,8 +29,6 @@
 import android.app.ambientcontext.IAmbientContextManager;
 import android.app.appsearch.AppSearchManagerFrameworkInitializer;
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
-import android.app.cloudsearch.CloudSearchManager;
-import android.app.cloudsearch.ICloudSearchManager;
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.IContentSuggestionsManager;
 import android.app.job.JobSchedulerFrameworkInitializer;
@@ -83,6 +81,8 @@
 import android.content.pm.verify.domain.IDomainVerificationManager;
 import android.content.res.Resources;
 import android.content.rollback.RollbackManagerFrameworkInitializer;
+import android.credentials.CredentialManager;
+import android.credentials.ICredentialManager;
 import android.debug.AdbManager;
 import android.debug.IAdbManager;
 import android.graphics.fonts.FontManager;
@@ -113,6 +113,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
+import android.healthconnect.HealthServicesInitializer;
 import android.location.CountryDetector;
 import android.location.ICountryDetector;
 import android.location.ILocationManager;
@@ -1139,6 +1140,19 @@
                 return new AutofillManager(ctx.getOuterContext(), service);
             }});
 
+        registerService(Context.CREDENTIAL_SERVICE, CredentialManager.class,
+                new CachedServiceFetcher<CredentialManager>() {
+                    @Override
+                    public CredentialManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getService(Context.CREDENTIAL_SERVICE);
+                        ICredentialManager service = ICredentialManager.Stub.asInterface(b);
+                        if (service != null) {
+                            return new CredentialManager(ctx.getOuterContext(), service);
+                        }
+                        return null;
+                    }});
+
         registerService(Context.MUSIC_RECOGNITION_SERVICE, MusicRecognitionManager.class,
                 new CachedServiceFetcher<MusicRecognitionManager>() {
                     @Override
@@ -1220,17 +1234,6 @@
                 }
             });
 
-        registerService(Context.CLOUDSEARCH_SERVICE, CloudSearchManager.class,
-            new CachedServiceFetcher<CloudSearchManager>() {
-                @Override
-                public CloudSearchManager createService(ContextImpl ctx)
-                    throws ServiceNotFoundException {
-                    IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE);
-                    return b == null ? null :
-                        new CloudSearchManager(ICloudSearchManager.Stub.asInterface(b));
-                }
-            });
-
         registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
                 new CachedServiceFetcher<AppPredictionManager>() {
             @Override
@@ -1537,6 +1540,7 @@
             BluetoothFrameworkInitializer.registerServiceWrappers();
             TelephonyFrameworkInitializer.registerServiceWrappers();
             AppSearchManagerFrameworkInitializer.initialize();
+            HealthServicesInitializer.registerServiceWrappers();
             WifiFrameworkInitializer.registerServiceWrappers();
             StatsFrameworkInitializer.registerServiceWrappers();
             RollbackManagerFrameworkInitializer.initialize();
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index ad7c53b..2718054 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -173,6 +173,15 @@
     public static final int FLAG_DONT_USE_ACCESSIBILITY = 0x00000002;
 
     /**
+     * UiAutomation sets {@link AccessibilityServiceInfo#isAccessibilityTool()} true by default.
+     * This flag provides the option to set this field false for tests exercising that property.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_NOT_ACCESSIBILITY_TOOL = 0x00000004;
+
+    /**
      * Returned by {@link #getAdoptedShellPermissions} to indicate that all permissions have been
      * adopted using {@link #adoptShellPermissionIdentity}.
      *
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index d17f2ba..0201c12 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
@@ -35,6 +37,7 @@
 import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.util.Log;
+import android.util.Pair;
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -46,6 +49,8 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.IAccessibilityManager;
 import android.window.ScreenCapture;
+import android.window.ScreenCapture.CaptureArgs;
+import android.window.ScreenCapture.ScreenCaptureListener;
 
 import libcore.io.IoUtils;
 
@@ -218,20 +223,22 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            int width = crop.width();
-            int height = crop.height();
-            final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-            final ScreenCapture.DisplayCaptureArgs captureArgs =
-                    new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
-                            .setSourceCrop(crop)
-                            .setSize(width, height)
-                            .build();
+            final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
+                    .setSourceCrop(crop)
+                    .build();
+            Pair<ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
+                    ScreenCapture.createSyncCaptureListener();
+            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
+                    syncScreenCapture.first);
             final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                    ScreenCapture.captureDisplay(captureArgs);
+                    syncScreenCapture.second.get();
             return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+        } catch (RemoteException re) {
+            re.rethrowAsRuntimeException();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
+        return null;
     }
 
     @Nullable
@@ -551,7 +558,9 @@
         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
-        info.setAccessibilityTool(true);
+        if ((flags & UiAutomation.FLAG_NOT_ACCESSIBILITY_TOOL) == 0) {
+            info.setAccessibilityTool(true);
+        }
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
diff --git a/core/java/android/app/cloudsearch/CloudSearchManager.java b/core/java/android/app/cloudsearch/CloudSearchManager.java
index 471e423..b7bbf47 100644
--- a/core/java/android/app/cloudsearch/CloudSearchManager.java
+++ b/core/java/android/app/cloudsearch/CloudSearchManager.java
@@ -15,17 +15,15 @@
  */
 package android.app.cloudsearch;
 
-import static java.util.Objects.requireNonNull;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
-import android.os.RemoteException;
 
 import java.util.concurrent.Executor;
+
 /**
  * A {@link CloudSearchManager} is the  class having all the information passed to search providers.
  *
@@ -41,7 +39,7 @@
         /**
          * Invoked by receiving app with the result of the search.
          *
-         * @param request original request for the search.
+         * @param request  original request for the search.
          * @param response search result.
          */
         void onSearchSucceeded(@NonNull SearchRequest request, @NonNull SearchResponse response);
@@ -51,17 +49,15 @@
          * Each failure is recorded. The client may receive a failure from one provider and
          * subsequently receive successful searches from other providers
          *
-         * @param request original request for the search.
+         * @param request  original request for the search.
          * @param response search result.
          */
         void onSearchFailed(@NonNull SearchRequest request, @NonNull SearchResponse response);
     }
 
-    private final ICloudSearchManager mService;
-
     /** @hide **/
-    public CloudSearchManager(@NonNull ICloudSearchManager service) {
-        mService = service;
+    public CloudSearchManager() {
+
     }
 
     /**
@@ -69,10 +65,9 @@
      * to the designated cloud lookup services.  After the lookup is done, the given
      * callback will be invoked by the system with the result or lack thereof.
      *
-     * @param request request to be searched.
+     * @param request          request to be searched.
      * @param callbackExecutor where the callback is invoked.
-     * @param callback invoked when the result is available.
-     *
+     * @param callback         invoked when the result is available.
      * @hide
      */
     @SystemApi
@@ -80,49 +75,8 @@
     public void search(@NonNull SearchRequest request,
             @NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull CallBack callback) {
-        try {
-            mService.search(
-                    requireNonNull(request),
-                    new CallBackWrapper(
-                        requireNonNull(request),
-                        requireNonNull(callback),
-                        requireNonNull(callbackExecutor)));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private final class CallBackWrapper extends
-            ICloudSearchManagerCallback.Stub {
-        @NonNull
-        private final SearchRequest mSearchRequest;
-
-        @NonNull
-        private final CallBack mCallback;
-
-        @NonNull
-        private final Executor mCallbackExecutor;
-
-        CallBackWrapper(
-                SearchRequest searchRequest,
-                CallBack callback,
-                Executor callbackExecutor) {
-            mSearchRequest = searchRequest;
-            mCallback = callback;
-            mCallbackExecutor = callbackExecutor;
-        }
-
-
-        @Override
-        public void onSearchSucceeded(SearchResponse searchResponse) {
-            mCallbackExecutor.execute(
-                    () -> mCallback.onSearchSucceeded(mSearchRequest, searchResponse));
-        }
-
-        @Override
-        public void onSearchFailed(SearchResponse searchResponse) {
-            mCallbackExecutor.execute(
-                    () -> mCallback.onSearchFailed(mSearchRequest, searchResponse));
-        }
+        callbackExecutor.execute(
+                () -> callback.onSearchFailed(request,
+                        new SearchResponse.Builder(SearchResponse.SEARCH_STATUS_UNKNOWN).build()));
     }
 }
diff --git a/core/java/android/app/cloudsearch/ICloudSearchManager.aidl b/core/java/android/app/cloudsearch/ICloudSearchManager.aidl
deleted file mode 100644
index 18f8fc4..0000000
--- a/core/java/android/app/cloudsearch/ICloudSearchManager.aidl
+++ /dev/null
@@ -1,33 +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.app.cloudsearch;
-
-import android.app.cloudsearch.SearchRequest;
-import android.app.cloudsearch.SearchResponse;
-import android.app.cloudsearch.ICloudSearchManagerCallback;
-
-/**
- * Used by {@link CloudSearchManager} to tell system server to do search.
- *
- * @hide
- */
-oneway interface ICloudSearchManager {
-  void search(in SearchRequest request, in ICloudSearchManagerCallback callBack);
-
-  void returnResults(in IBinder token, in String requestId,
-                     in SearchResponse response);
-}
diff --git a/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl b/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl
deleted file mode 100644
index 84771dd..0000000
--- a/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl
+++ /dev/null
@@ -1,31 +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.app.cloudsearch;
-
-import android.app.cloudsearch.SearchResponse;
-
-
-/**
- * Callback used by system server to notify invoker of {@link CloudSearchManager} of the result
- *
- * @hide
- */
-oneway interface ICloudSearchManagerCallback {
-  void onSearchSucceeded(in SearchResponse response);
-
-  void onSearchFailed(in SearchResponse response);
-}
diff --git a/core/java/android/app/cloudsearch/SearchRequest.aidl b/core/java/android/app/cloudsearch/SearchRequest.aidl
deleted file mode 100644
index 9f2cdb8..0000000
--- a/core/java/android/app/cloudsearch/SearchRequest.aidl
+++ /dev/null
@@ -1,19 +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.app.cloudsearch;
-
-parcelable SearchRequest;
diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java
index bf78325..3725b36 100644
--- a/core/java/android/app/cloudsearch/SearchRequest.java
+++ b/core/java/android/app/cloudsearch/SearchRequest.java
@@ -15,8 +15,6 @@
  */
 package android.app.cloudsearch;
 
-import static java.util.Objects.requireNonNull;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
@@ -28,7 +26,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /**
  * A {@link SearchRequest} is the data class having all the information passed to search providers.
@@ -39,36 +36,6 @@
 public final class SearchRequest implements Parcelable {
 
     /**
-     * Query for search.
-     */
-    @NonNull
-    private final String mQuery;
-
-    /**
-     * Expected result offset for pagination.
-     *
-     * The default value is 0.
-    */
-    private final int mResultOffset;
-
-    /**
-     * Expected search result number.
-     *
-     * The default value is 10.
-     */
-    private final int mResultNumber;
-
-    /**
-     * The max acceptable latency.
-     *
-     * The default value is 200 milliseconds.
-     */
-    private final float mMaxLatencyMillis;
-
-    @Nullable
-    private String mId = null;
-
-    /**
      * List of public static KEYS for the Bundle to  mSearchConstraints. mSearchConstraints
      * contains various constraints specifying the search intent.
      *
@@ -76,121 +43,83 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = {"CONSTRAINT_"},
-        value = {CONSTRAINT_IS_PRESUBMIT_SUGGESTION,
-            CONSTRAINT_SEARCH_PROVIDER_FILTER})
-    public @interface SearchConstraintKey {}
-    /** If this is a presubmit suggestion, Boolean value expected.
-     *  presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or
-     *  "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit.
+            value = {CONSTRAINT_IS_PRESUBMIT_SUGGESTION,
+                    CONSTRAINT_SEARCH_PROVIDER_FILTER})
+    public @interface SearchConstraintKey {
+    }
+
+    /**
+     * If this is a presubmit suggestion, Boolean value expected.
+     * presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or
+     * "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit.
      */
     public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION =
             "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION";
-    /** The target search provider list of package names(separated by ;), String value expected.
+    /**
+     * The target search provider list of package names(separated by ;), String value expected.
      * If this is not provided or its value is empty, then no filter will be applied.
      */
     public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER =
             "android.app.cloudsearch.SEARCH_PROVIDER_FILTER";
 
-    @NonNull
-    private Bundle mSearchConstraints;
-
-    /** Auto set by system servier, and the caller cannot set it.
-     *
-     * The caller's package name.
-     *
-     */
-    @NonNull
-    private String mCallerPackageName;
-
-    private SearchRequest(Parcel in) {
-        this.mQuery = in.readString();
-        this.mResultOffset = in.readInt();
-        this.mResultNumber = in.readInt();
-        this.mMaxLatencyMillis = in.readFloat();
-        this.mSearchConstraints = in.readBundle();
-        this.mId = in.readString();
-        this.mCallerPackageName = in.readString();
-    }
-
-    private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis,
-            Bundle searchConstraints, String callerPackageName) {
-        mQuery = query;
-        mResultOffset = resultOffset;
-        mResultNumber = resultNumber;
-        mMaxLatencyMillis = maxLatencyMillis;
-        mSearchConstraints = searchConstraints;
-        mCallerPackageName = callerPackageName;
+    private SearchRequest() {
     }
 
     /** Returns the original query. */
     @NonNull
     public String getQuery() {
-        return mQuery;
+        return "";
     }
 
     /** Returns the result offset. */
     public int getResultOffset() {
-        return mResultOffset;
+        return 0;
     }
 
     /** Returns the expected number of search results. */
     public int getResultNumber() {
-        return mResultNumber;
+        return 0;
     }
 
     /** Returns the maximum latency requirement. */
     public float getMaxLatencyMillis() {
-        return mMaxLatencyMillis;
+        return 0;
     }
 
     /** Returns the search constraints. */
     @NonNull
     public Bundle getSearchConstraints() {
-        return mSearchConstraints;
+        return Bundle.EMPTY;
     }
 
     /** Gets the caller's package name. */
     @NonNull
     public String getCallerPackageName() {
-        return mCallerPackageName;
+        return "";
     }
 
     /** Returns the search request id, which is used to identify the request. */
     @NonNull
     public String getRequestId() {
-        if (mId == null || mId.length() == 0) {
-            mId = String.valueOf(toString().hashCode());
-        }
-
-        return mId;
+        return "";
     }
 
-    /** Sets the caller, and this will be set by the system server.
+    /**
+     * Sets the caller, and this will be set by the system server.
      *
      * @hide
      */
     public void setCallerPackageName(@NonNull String callerPackageName) {
-        this.mCallerPackageName = callerPackageName;
-    }
-
-    private SearchRequest(Builder b) {
-        mQuery = requireNonNull(b.mQuery);
-        mResultOffset = b.mResultOffset;
-        mResultNumber = b.mResultNumber;
-        mMaxLatencyMillis = b.mMaxLatencyMillis;
-        mSearchConstraints = requireNonNull(b.mSearchConstraints);
-        mCallerPackageName = requireNonNull(b.mCallerPackageName);
     }
 
     /**
      * @see Creator
-     *
      */
     @NonNull
     public static final Creator<SearchRequest> CREATOR = new Creator<SearchRequest>() {
         @Override
         public SearchRequest createFromParcel(Parcel p) {
-            return new SearchRequest(p);
+            return new SearchRequest();
         }
 
         @Override
@@ -201,13 +130,6 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(this.mQuery);
-        dest.writeInt(this.mResultOffset);
-        dest.writeInt(this.mResultNumber);
-        dest.writeFloat(this.mMaxLatencyMillis);
-        dest.writeBundle(this.mSearchConstraints);
-        dest.writeString(getRequestId());
-        dest.writeString(this.mCallerPackageName);
     }
 
     @Override
@@ -217,44 +139,17 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        SearchRequest that = (SearchRequest) obj;
-        return Objects.equals(mQuery, that.mQuery)
-                && mResultOffset == that.mResultOffset
-                && mResultNumber == that.mResultNumber
-                && mMaxLatencyMillis == that.mMaxLatencyMillis
-                && Objects.equals(mSearchConstraints, that.mSearchConstraints)
-                && Objects.equals(mCallerPackageName, that.mCallerPackageName);
+        return false;
     }
 
     @Override
     public String toString() {
-        boolean isPresubmit =
-                mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION)
-                        && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION);
-
-        String searchProvider = "EMPTY";
-        if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) {
-            searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER);
-        }
-
-        return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;"
-                        + "is_presubmit:%b;search_provider:%s;callerPackageName:%s}", mQuery,
-                mResultOffset, mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider,
-                mCallerPackageName);
+        return "";
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
-                mSearchConstraints, mCallerPackageName);
+        return 0;
     }
 
     /**
@@ -264,87 +159,62 @@
      */
     @SystemApi
     public static final class Builder {
-        private String mQuery;
-        private int mResultOffset;
-        private int mResultNumber;
-        private float mMaxLatencyMillis;
-        private Bundle mSearchConstraints;
-        private String mCallerPackageName;
-
         /**
-         *
          * @param query the query for search.
-         *
          * @hide
          */
         @SystemApi
         public Builder(@NonNull String query) {
-            mQuery = query;
-
-            mResultOffset = 0;
-            mResultNumber = 10;
-            mMaxLatencyMillis = 200;
-            mSearchConstraints = Bundle.EMPTY;
-            mCallerPackageName = "DEFAULT_CALLER";
         }
 
         /** Sets the input query. */
         @NonNull
         public Builder setQuery(@NonNull String query) {
-            this.mQuery = query;
             return this;
         }
 
         /** Sets the search result offset. */
         @NonNull
         public Builder setResultOffset(int resultOffset) {
-            this.mResultOffset = resultOffset;
             return this;
         }
 
         /** Sets the expected number of search result. */
         @NonNull
         public Builder setResultNumber(int resultNumber) {
-            this.mResultNumber = resultNumber;
             return this;
         }
 
         /** Sets the maximum acceptable search latency. */
         @NonNull
         public Builder setMaxLatencyMillis(float maxLatencyMillis) {
-            this.mMaxLatencyMillis = maxLatencyMillis;
             return this;
         }
 
-        /** Sets the search constraints, such as the user location, the search type(presubmit or
-         * postsubmit), and the target search providers. */
+        /**
+         * Sets the search constraints, such as the user location, the search type(presubmit or
+         * postsubmit), and the target search providers.
+         */
         @NonNull
         public Builder setSearchConstraints(@Nullable Bundle searchConstraints) {
-            this.mSearchConstraints = searchConstraints;
             return this;
         }
 
-        /** Sets the caller, and this will be set by the system server.
+        /**
+         * Sets the caller, and this will be set by the system server.
          *
          * @hide
          */
         @NonNull
         @TestApi
         public Builder setCallerPackageName(@NonNull String callerPackageName) {
-            this.mCallerPackageName = callerPackageName;
             return this;
         }
 
         /** Builds a SearchRequest based-on the given params. */
         @NonNull
         public SearchRequest build() {
-            if (mQuery == null || mResultOffset < 0 || mResultNumber < 1 || mMaxLatencyMillis < 0
-                    || mSearchConstraints == null) {
-                throw new IllegalStateException("Please make sure all required args are valid.");
-            }
-
-            return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
-                               mSearchConstraints, mCallerPackageName);
+            return new SearchRequest();
         }
     }
 }
diff --git a/core/java/android/app/cloudsearch/SearchResponse.aidl b/core/java/android/app/cloudsearch/SearchResponse.aidl
deleted file mode 100644
index 2064d11..0000000
--- a/core/java/android/app/cloudsearch/SearchResponse.aidl
+++ /dev/null
@@ -1,19 +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.app.cloudsearch;
-
-parcelable SearchResponse;
\ No newline at end of file
diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java
index 607bd56..c86142e 100644
--- a/core/java/android/app/cloudsearch/SearchResponse.java
+++ b/core/java/android/app/cloudsearch/SearchResponse.java
@@ -15,8 +15,6 @@
  */
 package android.app.cloudsearch;
 
-import static java.util.Objects.requireNonNull;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -25,7 +23,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * A {@link SearchResponse} includes search results and associated meta information.
@@ -37,77 +34,53 @@
     /** @hide */
     @IntDef(prefix = {"SEARCH_STATUS_"},
             value = {SEARCH_STATUS_UNKNOWN,
-                     SEARCH_STATUS_OK,
-                     SEARCH_STATUS_TIME_OUT,
-                     SEARCH_STATUS_NO_INTERNET})
-    public @interface SearchStatusCode {}
+                    SEARCH_STATUS_OK,
+                    SEARCH_STATUS_TIME_OUT,
+                    SEARCH_STATUS_NO_INTERNET})
+    public @interface SearchStatusCode {
+    }
+
     public static final int SEARCH_STATUS_UNKNOWN = -1;
     public static final int SEARCH_STATUS_OK = 0;
     public static final int SEARCH_STATUS_TIME_OUT = 1;
     public static final int SEARCH_STATUS_NO_INTERNET = 2;
 
-    private final int mStatusCode;
-
-    /** Auto set by system servier, and the provider cannot set it. */
-    @NonNull
-    private String mSource;
-
-    @NonNull
-    private final List<SearchResult> mSearchResults;
-
-    private SearchResponse(Parcel in) {
-        this.mStatusCode = in.readInt();
-        this.mSource = in.readString();
-        this.mSearchResults = in.createTypedArrayList(SearchResult.CREATOR);
-    }
-
-    private SearchResponse(@SearchStatusCode int statusCode,  String source,
-                           List<SearchResult> searchResults) {
-        mStatusCode = statusCode;
-        mSource = source;
-        mSearchResults = searchResults;
+    private SearchResponse() {
     }
 
     /** Gets the search status code. */
     public int getStatusCode() {
-        return mStatusCode;
+        return SEARCH_STATUS_UNKNOWN;
     }
 
     /** Gets the search provider package name. */
     @NonNull
     public String getSource() {
-        return mSource;
+        return "";
     }
 
     /** Gets the search results, which can be empty. */
     @NonNull
     public List<SearchResult> getSearchResults() {
-        return mSearchResults;
+        return new ArrayList<SearchResult>();
     }
 
-    /** Sets the search provider, and this will be set by the system server.
+    /**
+     * Sets the search provider, and this will be set by the system server.
      *
      * @hide
      */
     public void setSource(@NonNull String source) {
-        this.mSource = source;
-    }
-
-    private SearchResponse(Builder b) {
-        mStatusCode = b.mStatusCode;
-        mSource = requireNonNull(b.mSource);
-        mSearchResults = requireNonNull(b.mSearchResults);
     }
 
     /**
-     *
      * @see Creator
-     *
      */
-    @NonNull public static final Creator<SearchResponse> CREATOR = new Creator<SearchResponse>() {
+    @NonNull
+    public static final Creator<SearchResponse> CREATOR = new Creator<SearchResponse>() {
         @Override
         public SearchResponse createFromParcel(Parcel p) {
-            return new SearchResponse(p);
+            return new SearchResponse();
         }
 
         @Override
@@ -118,9 +91,6 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(this.mStatusCode);
-        dest.writeString(this.mSource);
-        dest.writeTypedList(this.mSearchResults);
     }
 
     @Override
@@ -130,23 +100,12 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        SearchResponse that = (SearchResponse) obj;
-        return mStatusCode == that.mStatusCode
-                && Objects.equals(mSource, that.mSource)
-                && Objects.equals(mSearchResults, that.mSearchResults);
+        return false;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mStatusCode, mSource, mSearchResults);
+        return 0;
     }
 
     /**
@@ -156,59 +115,40 @@
      */
     @SystemApi
     public static final class Builder {
-        private int mStatusCode;
-        private String mSource;
-        private List<SearchResult> mSearchResults;
-
         /**
-         *
          * @param statusCode the search status code.
-         *
          * @hide
          */
         @SystemApi
         public Builder(@SearchStatusCode int statusCode) {
-            mStatusCode = statusCode;
-
-            /** Init with a default value. */
-            mSource = "DEFAULT";
-
-            mSearchResults = new ArrayList<SearchResult>();
         }
 
         /** Sets the search status code. */
         @NonNull
         public Builder setStatusCode(@SearchStatusCode int statusCode) {
-            this.mStatusCode = statusCode;
             return this;
         }
 
-        /** Sets the search provider, and this will be set by the system server.
+        /**
+         * Sets the search provider, and this will be set by the system server.
          *
          * @hide
          */
         @NonNull
         public Builder setSource(@NonNull String source) {
-            this.mSource = source;
             return this;
         }
 
         /** Sets the search results. */
         @NonNull
         public Builder setSearchResults(@NonNull List<SearchResult> searchResults) {
-            this.mSearchResults = searchResults;
             return this;
         }
 
         /** Builds a SearchResponse based-on the given parameters. */
         @NonNull
         public SearchResponse build() {
-            if (mStatusCode < SEARCH_STATUS_UNKNOWN || mStatusCode > SEARCH_STATUS_NO_INTERNET
-                    || mSearchResults == null) {
-                throw new IllegalStateException("Please make sure all @NonNull args are assigned.");
-            }
-
-            return new SearchResponse(mStatusCode, mSource, mSearchResults);
+            return new SearchResponse();
         }
     }
 }
diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java
index c6583b6..123c3a2 100644
--- a/core/java/android/app/cloudsearch/SearchResult.java
+++ b/core/java/android/app/cloudsearch/SearchResult.java
@@ -15,8 +15,6 @@
  */
 package android.app.cloudsearch;
 
-import static java.util.Objects.requireNonNull;
-
 import android.annotation.NonNull;
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
@@ -27,7 +25,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /**
  * A {@link SearchResult} includes all the information for one result item.
@@ -37,17 +34,6 @@
 @SystemApi
 public final class SearchResult implements Parcelable {
 
-    /** Short content best describing the result item. */
-    @NonNull
-    private final String mTitle;
-
-    /** Matched contents in the result item. */
-    @NonNull
-    private final String mSnippet;
-
-    /** Ranking Score provided by the search provider. */
-    private final float mScore;
-
     /**
      * List of public static KEYS for Bundles in mExtraInfos.
      * mExtraInfos contains various information specified for different data types.
@@ -56,28 +42,30 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = {"EXTRAINFO_"},
-        value = {EXTRAINFO_APP_DOMAIN_URL,
-            EXTRAINFO_APP_ICON,
-            EXTRAINFO_APP_DEVELOPER_NAME,
-            EXTRAINFO_APP_SIZE_BYTES,
-            EXTRAINFO_APP_STAR_RATING,
-            EXTRAINFO_APP_IARC,
-            EXTRAINFO_APP_REVIEW_COUNT,
-            EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER,
-            EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER,
-            EXTRAINFO_SHORT_DESCRIPTION,
-            EXTRAINFO_LONG_DESCRIPTION,
-            EXTRAINFO_SCREENSHOTS,
-            EXTRAINFO_APP_BADGES,
-            EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING,
-            EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING,
-            EXTRAINFO_ACTION_APP_CARD,
-            EXTRAINFO_ACTION_INSTALL_BUTTON,
-            EXTRAINFO_APP_PACKAGE_NAME,
-            EXTRAINFO_APP_INSTALL_COUNT,
-            EXTRAINFO_WEB_URL,
-            EXTRAINFO_WEB_ICON})
-    public @interface SearchResultExtraInfoKey {}
+            value = {EXTRAINFO_APP_DOMAIN_URL,
+                    EXTRAINFO_APP_ICON,
+                    EXTRAINFO_APP_DEVELOPER_NAME,
+                    EXTRAINFO_APP_SIZE_BYTES,
+                    EXTRAINFO_APP_STAR_RATING,
+                    EXTRAINFO_APP_IARC,
+                    EXTRAINFO_APP_REVIEW_COUNT,
+                    EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER,
+                    EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER,
+                    EXTRAINFO_SHORT_DESCRIPTION,
+                    EXTRAINFO_LONG_DESCRIPTION,
+                    EXTRAINFO_SCREENSHOTS,
+                    EXTRAINFO_APP_BADGES,
+                    EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING,
+                    EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING,
+                    EXTRAINFO_ACTION_APP_CARD,
+                    EXTRAINFO_ACTION_INSTALL_BUTTON,
+                    EXTRAINFO_APP_PACKAGE_NAME,
+                    EXTRAINFO_APP_INSTALL_COUNT,
+                    EXTRAINFO_WEB_URL,
+                    EXTRAINFO_WEB_ICON})
+    public @interface SearchResultExtraInfoKey {
+    }
+
     /** This App developer website's domain URL, String value expected. */
     public static final String EXTRAINFO_APP_DOMAIN_URL = "android.app.cloudsearch.APP_DOMAIN_URL";
     /** This App icon, android.graphics.drawable.Icon expected. */
@@ -90,7 +78,8 @@
     /** This App developer's name, Double value expected. */
     public static final String EXTRAINFO_APP_STAR_RATING =
             "android.app.cloudsearch.APP_STAR_RATING";
-    /** This App's IARC rating, String value expected.
+    /**
+     * This App's IARC rating, String value expected.
      * IARC (International Age Rating Coalition) is partnered globally with major
      * content rating organizations to provide a centralized and one-stop-shop for
      * rating content on a global scale.
@@ -142,62 +131,40 @@
     /** Web content's domain icon, android.graphics.drawable.Icon expected. */
     public static final String EXTRAINFO_WEB_ICON = "android.app.cloudsearch.WEB_ICON";
 
-    @NonNull
-    private Bundle mExtraInfos;
-
-    private SearchResult(Parcel in) {
-        this.mTitle = in.readString();
-        this.mSnippet = in.readString();
-        this.mScore = in.readFloat();
-        this.mExtraInfos = in.readBundle();
-    }
-
-    private SearchResult(String title, String snippet, float score, Bundle extraInfos) {
-        mTitle = title;
-        mSnippet = snippet;
-        mScore = score;
-        mExtraInfos = extraInfos;
+    private SearchResult() {
     }
 
     /** Gets the search result title. */
     @NonNull
     public String getTitle() {
-        return mTitle;
+        return "";
     }
 
     /** Gets the search result snippet. */
     @NonNull
     public String getSnippet() {
-        return mSnippet;
+        return "";
     }
 
     /** Gets the ranking score provided by the original search provider. */
     public float getScore() {
-        return mScore;
+        return 0;
     }
 
     /** Gets the extra information associated with the search result. */
     @NonNull
     public Bundle getExtraInfos() {
-        return mExtraInfos;
-    }
-
-    private SearchResult(Builder b) {
-        mTitle = requireNonNull(b.mTitle);
-        mSnippet = requireNonNull(b.mSnippet);
-        mScore = b.mScore;
-        mExtraInfos = requireNonNull(b.mExtraInfos);
+        return Bundle.EMPTY;
     }
 
     /**
-     *
      * @see Creator
-     *
      */
-    @NonNull public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() {
+    @NonNull
+    public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() {
         @Override
         public SearchResult createFromParcel(Parcel p) {
-            return new SearchResult(p);
+            return new SearchResult();
         }
 
         @Override
@@ -208,10 +175,6 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(this.mTitle);
-        dest.writeString(this.mSnippet);
-        dest.writeFloat(this.mScore);
-        dest.writeBundle(this.mExtraInfos);
     }
 
     @Override
@@ -221,24 +184,12 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        SearchResult that = (SearchResult) obj;
-        return Objects.equals(mTitle, that.mTitle)
-            && Objects.equals(mSnippet, that.mSnippet)
-            && mScore == that.mScore
-            && Objects.equals(mExtraInfos, that.mExtraInfos);
+        return false;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTitle, mSnippet, mScore, mExtraInfos);
+        return 0;
     }
 
     /**
@@ -248,63 +199,43 @@
      */
     @SystemApi
     public static final class Builder {
-        private String mTitle;
-        private String mSnippet;
-        private float mScore;
-        private Bundle mExtraInfos;
-
         /**
-         *
-         * @param title the title to the search result.
+         * @param title      the title to the search result.
          * @param extraInfos the extra infos associated with the search result.
-         *
          * @hide
          */
         @SystemApi
         public Builder(@NonNull String title, @NonNull Bundle extraInfos) {
-            mTitle = title;
-            mExtraInfos = extraInfos;
-
-            mSnippet = "";
-            mScore = 0;
         }
 
         /** Sets the title to the search result. */
         @NonNull
         public Builder setTitle(@NonNull String title) {
-            this.mTitle = title;
             return this;
         }
 
         /** Sets the snippet to the search result. */
         @NonNull
         public Builder setSnippet(@NonNull String snippet) {
-            this.mSnippet = snippet;
             return this;
         }
 
         /** Sets the ranking score to the search result. */
         @NonNull
         public Builder setScore(float score) {
-            this.mScore = score;
             return this;
         }
 
         /** Adds extra information to the search result for rendering in the UI. */
         @NonNull
         public Builder setExtraInfos(@NonNull Bundle extraInfos) {
-            this.mExtraInfos = extraInfos;
             return this;
         }
 
         /** Builds a SearchResult based-on the given parameters. */
         @NonNull
         public SearchResult build() {
-            if (mTitle == null || mExtraInfos == null || mSnippet == null) {
-                throw new IllegalStateException("Please make sure all required args are assigned.");
-            }
-
-            return new SearchResult(mTitle, mSnippet, mScore, mExtraInfos);
+            return new SearchResult();
         }
     }
 }
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index a7828ab..f4826ec 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -24,7 +24,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -48,9 +47,9 @@
  *
  * <p>The creator of an external suggestion is expected to be separate Android process, e.g. a
  * process integrating with the external time source via a HAL or local network. The creator must
- * capture the elapsed realtime reference clock, e.g. via {@link SystemClock#elapsedRealtime()},
- * when the Unix epoch time is first obtained (usually under a wakelock). This enables Android to
- * adjust for latency introduced between suggestion creation and eventual use. Adjustments for other
+ * capture the elapsed realtime clock value, e.g. via {@link SystemClock#elapsedRealtime()}, when
+ * the Unix epoch time is first obtained (usually under a wakelock). This enables Android to adjust
+ * for latency introduced between suggestion creation and eventual use. Adjustments for other
  * sources of latency, i.e. those before the external time suggestion is created, must be handled by
  * the creator.
  *
@@ -97,7 +96,7 @@
     public ExternalTimeSuggestion(@ElapsedRealtimeLong long elapsedRealtimeMillis,
             @CurrentTimeMillisLong long suggestionMillis) {
         mTimeSuggestionHelper = new TimeSuggestionHelper(ExternalTimeSuggestion.class,
-                new TimestampedValue<>(elapsedRealtimeMillis, suggestionMillis));
+                new UnixEpochTime(elapsedRealtimeMillis, suggestionMillis));
     }
 
     private ExternalTimeSuggestion(@NonNull TimeSuggestionHelper helper) {
@@ -118,7 +117,7 @@
      * {@hide}
      */
     @NonNull
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mTimeSuggestionHelper.getUnixEpochTime();
     }
 
diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java
index 44bc178..76bad58 100644
--- a/core/java/android/app/time/TimeCapabilities.java
+++ b/core/java/android/app/time/TimeCapabilities.java
@@ -57,21 +57,21 @@
     @NonNull
     private final UserHandle mUserHandle;
     private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
-    private final @CapabilityState int mSuggestManualTimeCapability;
+    private final @CapabilityState int mSetManualTimeCapability;
 
     private TimeCapabilities(@NonNull Builder builder) {
         this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
         this.mConfigureAutoDetectionEnabledCapability =
                 builder.mConfigureAutoDetectionEnabledCapability;
-        this.mSuggestManualTimeCapability = builder.mSuggestManualTimeCapability;
+        this.mSetManualTimeCapability = builder.mSetManualTimeCapability;
     }
 
     @NonNull
-    private static TimeCapabilities createFromParcel(Parcel in) {
+    private static TimeCapabilities createFromParcel(@NonNull Parcel in) {
         UserHandle userHandle = UserHandle.readFromParcel(in);
         return new TimeCapabilities.Builder(userHandle)
                 .setConfigureAutoDetectionEnabledCapability(in.readInt())
-                .setSuggestManualTimeCapability(in.readInt())
+                .setSetManualTimeCapability(in.readInt())
                 .build();
     }
 
@@ -79,7 +79,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         UserHandle.writeToParcel(mUserHandle, dest);
         dest.writeInt(mConfigureAutoDetectionEnabledCapability);
-        dest.writeInt(mSuggestManualTimeCapability);
+        dest.writeInt(mSetManualTimeCapability);
     }
 
     /**
@@ -94,11 +94,12 @@
 
     /**
      * Returns the capability state associated with the user's ability to manually set time on a
-     * device.
+     * device. The setting can be updated via {@link
+     * TimeManager#updateTimeConfiguration(TimeConfiguration)}.
      */
     @CapabilityState
-    public int getSuggestManualTimeCapability() {
-        return mSuggestManualTimeCapability;
+    public int getSetManualTimeCapability() {
+        return mSetManualTimeCapability;
     }
 
     /**
@@ -136,14 +137,14 @@
         TimeCapabilities that = (TimeCapabilities) o;
         return mConfigureAutoDetectionEnabledCapability
                 == that.mConfigureAutoDetectionEnabledCapability
-                && mSuggestManualTimeCapability == that.mSuggestManualTimeCapability
+                && mSetManualTimeCapability == that.mSetManualTimeCapability
                 && mUserHandle.equals(that.mUserHandle);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
-                mSuggestManualTimeCapability);
+                mSetManualTimeCapability);
     }
 
     @Override
@@ -152,7 +153,7 @@
                 + "mUserHandle=" + mUserHandle
                 + ", mConfigureAutoDetectionEnabledCapability="
                 + mConfigureAutoDetectionEnabledCapability
-                + ", mSuggestManualTimeCapability=" + mSuggestManualTimeCapability
+                + ", mSetManualTimeCapability=" + mSetManualTimeCapability
                 + '}';
     }
 
@@ -165,7 +166,7 @@
 
         @NonNull private final UserHandle mUserHandle;
         private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
-        private @CapabilityState int mSuggestManualTimeCapability;
+        private @CapabilityState int mSetManualTimeCapability;
 
         public Builder(@NonNull UserHandle userHandle) {
             this.mUserHandle = Objects.requireNonNull(userHandle);
@@ -176,18 +177,18 @@
             this.mUserHandle = timeCapabilities.mUserHandle;
             this.mConfigureAutoDetectionEnabledCapability =
                     timeCapabilities.mConfigureAutoDetectionEnabledCapability;
-            this.mSuggestManualTimeCapability = timeCapabilities.mSuggestManualTimeCapability;
+            this.mSetManualTimeCapability = timeCapabilities.mSetManualTimeCapability;
         }
 
-        /** Sets the state for automatic time detection config. */
+        /** Sets the value for the "configure automatic time detection" capability. */
         public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
             this.mConfigureAutoDetectionEnabledCapability = value;
             return this;
         }
 
-        /** Sets the state for manual time change. */
-        public Builder setSuggestManualTimeCapability(@CapabilityState int value) {
-            this.mSuggestManualTimeCapability = value;
+        /** Sets the value for the "set manual time" capability. */
+        public Builder setSetManualTimeCapability(@CapabilityState int value) {
+            this.mSetManualTimeCapability = value;
             return this;
         }
 
@@ -195,7 +196,7 @@
         public TimeCapabilities build() {
             verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
                     "configureAutoDetectionEnabledCapability");
-            verifyCapabilitySet(mSuggestManualTimeCapability, "mSuggestManualTimeCapability");
+            verifyCapabilitySet(mSetManualTimeCapability, "mSetManualTimeCapability");
             return new TimeCapabilities(this);
         }
 
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index be4d010..b6a0818 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -71,8 +71,6 @@
 
     /**
      * Returns the user's time behaviour capabilities.
-     *
-     * @hide
      */
     @NonNull
     public TimeCapabilities getCapabilities() {
@@ -81,8 +79,6 @@
 
     /**
      * Returns the user's time behaviour configuration.
-     *
-     * @hide
      */
     @NonNull
     public TimeConfiguration getConfiguration() {
diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java
index 11f6ed2..7d98698 100644
--- a/core/java/android/app/time/TimeConfiguration.java
+++ b/core/java/android/app/time/TimeConfiguration.java
@@ -55,10 +55,16 @@
                 }
             };
 
+    /**
+     * All configuration properties
+     *
+     * @hide
+     */
     @StringDef(SETTING_AUTO_DETECTION_ENABLED)
     @Retention(RetentionPolicy.SOURCE)
     @interface Setting {}
 
+    /** See {@link TimeConfiguration#isAutoDetectionEnabled()} for details. */
     @Setting
     private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index d6acb8c..9f66f09 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -21,7 +21,9 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.timedetector.ITimeDetectorService;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -274,4 +276,149 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns a snapshot of the device's current system clock time state. See also {@link
+     * #confirmTime(UnixEpochTime)} for how this information can be used.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    @NonNull
+    public TimeState getTimeState() {
+        if (DEBUG) {
+            Log.d(TAG, "getTimeState called");
+        }
+        try {
+            return mITimeDetectorService.getTimeState();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Confirms the device's current time during device setup, raising the system's confidence in
+     * the time if needed. Unlike {@link #setManualTime(UnixEpochTime)}, which can only be used when
+     * automatic time detection is currently disabled, this method can be used regardless of the
+     * automatic time detection setting, but only to confirm the current time (which may have been
+     * set via automatic means). Use {@link #getTimeState()} to obtain the time state to confirm.
+     *
+     * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
+     * confirmed is no longer the time the device is currently set to. Confirming a time
+     * in which the system already has high confidence will return {@code true}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
+        if (DEBUG) {
+            Log.d(TAG, "confirmTime called: " + unixEpochTime);
+        }
+        try {
+            return mITimeDetectorService.confirmTime(unixEpochTime);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attempts to set the device's time, expected to be determined from the user's manually entered
+     * information.
+     *
+     * <p>Returns {@code false} if the time is invalid, or the device configuration / user
+     * capabilities prevents the time being accepted, e.g. if the device is currently set to
+     * "automatic time detection". This method returns {@code true} if the time was accepted even
+     * if it is the same as the current device time.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
+        if (DEBUG) {
+            Log.d(TAG, "setTime called: " + unixEpochTime);
+        }
+        try {
+            ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(unixEpochTime);
+            manualTimeSuggestion.addDebugInfo("TimeManager.setTime()");
+            manualTimeSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
+            manualTimeSuggestion.addDebugInfo("UserHandle: " + android.os.Process.myUserHandle());
+            manualTimeSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
+            return mITimeDetectorService.setManualTime(manualTimeSuggestion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a snapshot of the device's current time zone state. See also {@link
+     * #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
+     * be used.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    @NonNull
+    public TimeZoneState getTimeZoneState() {
+        if (DEBUG) {
+            Log.d(TAG, "getTimeZoneState called");
+        }
+        try {
+            return mITimeZoneDetectorService.getTimeZoneState();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Confirms the device's current time zone ID, raising the system's confidence in the time zone
+     * if needed. Unlike {@link #setManualTimeZone(String)}, which can only be used when automatic
+     * time zone detection is currently disabled, this method can be used regardless of the
+     * automatic time zone detection setting, but only to confirm the current value (which may have
+     * been set via automatic means).
+     *
+     * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
+     * confirmed is no longer the time zone ID the device is currently set to. Confirming a time
+     * zone ID in which the system already has high confidence returns {@code true}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public boolean confirmTimeZone(@NonNull String timeZoneId) {
+        if (DEBUG) {
+            Log.d(TAG, "confirmTimeZone called: " + timeZoneId);
+        }
+        try {
+            return mITimeZoneDetectorService.confirmTimeZone(timeZoneId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attempts to set the device's time zone, expected to be determined from a user's manually
+     * entered information.
+     *
+     * <p>Returns {@code false} if the time zone is invalid, or the device configuration / user
+     * capabilities prevents the time zone being accepted, e.g. if the device is currently set to
+     * "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
+     * time zone that is accepted and matches the current device time zone returns {@code true}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+    public boolean setManualTimeZone(@NonNull String timeZoneId) {
+        if (DEBUG) {
+            Log.d(TAG, "setManualTimeZone called: " + timeZoneId);
+        }
+        try {
+            ManualTimeZoneSuggestion manualTimeZoneSuggestion =
+                    new ManualTimeZoneSuggestion(timeZoneId);
+            manualTimeZoneSuggestion.addDebugInfo("TimeManager.setManualTimeZone()");
+            manualTimeZoneSuggestion.addDebugInfo("UID: " + android.os.Process.myUid());
+            manualTimeZoneSuggestion.addDebugInfo("Process: " + android.os.Process.myProcessName());
+            return mITimeZoneDetectorService.setManualTimeZone(manualTimeZoneSuggestion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/app/time/TimeState.aidl
similarity index 75%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/app/time/TimeState.aidl
index daebfbf..70c31d8 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/app/time/TimeState.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.app.time;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable TimeState;
diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java
new file mode 100644
index 0000000..01c869d
--- /dev/null
+++ b/core/java/android/app/time/TimeState.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A snapshot of the system time state.
+ *
+ * <p>{@code mUnixEpochTime} contains a snapshot of the system clock time and elapsed realtime clock
+ * time.
+ *
+ * <p>{@code mUserShouldConfirmTime} is {@code true} if the system has low confidence in the system
+ * clock time.
+ *
+ * @hide
+ */
+public final class TimeState implements Parcelable {
+
+    public static final @NonNull Creator<TimeState> CREATOR = new Creator<>() {
+        public TimeState createFromParcel(Parcel in) {
+            return TimeState.createFromParcel(in);
+        }
+
+        public TimeState[] newArray(int size) {
+            return new TimeState[size];
+        }
+    };
+
+    @NonNull private final UnixEpochTime mUnixEpochTime;
+    private final boolean mUserShouldConfirmTime;
+
+    /** @hide */
+    public TimeState(@NonNull UnixEpochTime unixEpochTime, boolean userShouldConfirmTime) {
+        mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
+        mUserShouldConfirmTime = userShouldConfirmTime;
+    }
+
+    private static TimeState createFromParcel(Parcel in) {
+        UnixEpochTime unixEpochTime = in.readParcelable(null, UnixEpochTime.class);
+        boolean userShouldConfirmId = in.readBoolean();
+        return new TimeState(unixEpochTime, userShouldConfirmId);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mUnixEpochTime, 0);
+        dest.writeBoolean(mUserShouldConfirmTime);
+    }
+
+    /** @hide */
+    @Nullable
+    public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) {
+        Long elapsedRealtimeMillis = null;
+        Long unixEpochTimeMillis = null;
+        Boolean userShouldConfirmTime = null;
+        String opt;
+        while ((opt = cmd.getNextArg()) != null) {
+            switch (opt) {
+                case "--elapsed_realtime": {
+                    elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                    break;
+                }
+                case "--unix_epoch_time": {
+                    unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                    break;
+                }
+                case "--user_should_confirm_time": {
+                    userShouldConfirmTime  = Boolean.parseBoolean(cmd.getNextArgRequired());
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+
+        if (elapsedRealtimeMillis == null) {
+            throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
+        }
+        if (unixEpochTimeMillis == null) {
+            throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+        }
+        if (userShouldConfirmTime == null) {
+            throw new IllegalArgumentException("No userShouldConfirmTime specified.");
+        }
+
+        UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+        return new TimeState(unixEpochTime, userShouldConfirmTime);
+    }
+
+    /** @hide */
+    public static void printCommandLineOpts(@NonNull PrintWriter pw) {
+        pw.println("TimeState options:");
+        pw.println("  --elapsed_realtime <elapsed realtime millis>");
+        pw.println("  --unix_epoch_time <Unix epoch time millis>");
+        pw.println("  --user_should_confirm_time {true|false}");
+        pw.println();
+        pw.println("See " + TimeState.class.getName() + " for more information");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public UnixEpochTime getUnixEpochTime() {
+        return mUnixEpochTime;
+    }
+
+    public boolean getUserShouldConfirmTime() {
+        return mUserShouldConfirmTime;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeState that = (TimeState) o;
+        return Objects.equals(mUnixEpochTime, that.mUnixEpochTime)
+                && mUserShouldConfirmTime == that.mUserShouldConfirmTime;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUnixEpochTime, mUserShouldConfirmTime);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeState{"
+                + "mUnixEpochTime=" + mUnixEpochTime
+                + ", mUserShouldConfirmTime=" + mUserShouldConfirmTime
+                + '}';
+    }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 895a8e4..2f147ce 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -22,8 +22,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.time.Capabilities.CapabilityState;
-import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneDetector;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -43,16 +41,15 @@
 @SystemApi
 public final class TimeZoneCapabilities implements Parcelable {
 
-    public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
-            new Creator<TimeZoneCapabilities>() {
-                public TimeZoneCapabilities createFromParcel(Parcel in) {
-                    return TimeZoneCapabilities.createFromParcel(in);
-                }
+    public static final @NonNull Creator<TimeZoneCapabilities> CREATOR = new Creator<>() {
+        public TimeZoneCapabilities createFromParcel(Parcel in) {
+            return TimeZoneCapabilities.createFromParcel(in);
+        }
 
-                public TimeZoneCapabilities[] newArray(int size) {
-                    return new TimeZoneCapabilities[size];
-                }
-            };
+        public TimeZoneCapabilities[] newArray(int size) {
+            return new TimeZoneCapabilities[size];
+        }
+    };
 
     /**
      * The user the capabilities are for. This is used for object equality and debugging but there
@@ -61,7 +58,7 @@
     @NonNull private final UserHandle mUserHandle;
     private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
     private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
-    private final @CapabilityState int mSuggestManualTimeZoneCapability;
+    private final @CapabilityState int mSetManualTimeZoneCapability;
 
     private TimeZoneCapabilities(@NonNull Builder builder) {
         this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
@@ -69,16 +66,16 @@
                 builder.mConfigureAutoDetectionEnabledCapability;
         this.mConfigureGeoDetectionEnabledCapability =
                 builder.mConfigureGeoDetectionEnabledCapability;
-        this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
+        this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
     }
 
     @NonNull
-    private static TimeZoneCapabilities createFromParcel(Parcel in) {
+    private static TimeZoneCapabilities createFromParcel(@NonNull Parcel in) {
         UserHandle userHandle = UserHandle.readFromParcel(in);
         return new TimeZoneCapabilities.Builder(userHandle)
                 .setConfigureAutoDetectionEnabledCapability(in.readInt())
                 .setConfigureGeoDetectionEnabledCapability(in.readInt())
-                .setSuggestManualTimeZoneCapability(in.readInt())
+                .setSetManualTimeZoneCapability(in.readInt())
                 .build();
     }
 
@@ -87,7 +84,7 @@
         UserHandle.writeToParcel(mUserHandle, dest);
         dest.writeInt(mConfigureAutoDetectionEnabledCapability);
         dest.writeInt(mConfigureGeoDetectionEnabledCapability);
-        dest.writeInt(mSuggestManualTimeZoneCapability);
+        dest.writeInt(mSetManualTimeZoneCapability);
     }
 
     /**
@@ -112,17 +109,17 @@
 
     /**
      * Returns the capability state associated with the user's ability to manually set the time zone
-     * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+     * on a device.
      *
-     * <p>The suggestion will be ignored in all cases unless the value is {@link
+     * <p>The time zone will be ignored in all cases unless the value is {@link
      * Capabilities#CAPABILITY_POSSESSED}. See also
      * {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
      *
      * @hide
      */
     @CapabilityState
-    public int getSuggestManualTimeZoneCapability() {
-        return mSuggestManualTimeZoneCapability;
+    public int getSetManualTimeZoneCapability() {
+        return mSetManualTimeZoneCapability;
     }
 
     /**
@@ -174,13 +171,13 @@
                 == that.mConfigureAutoDetectionEnabledCapability
                 && mConfigureGeoDetectionEnabledCapability
                 == that.mConfigureGeoDetectionEnabledCapability
-                && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
+                && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
-                mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
+                mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability);
     }
 
     @Override
@@ -191,17 +188,21 @@
                 + mConfigureAutoDetectionEnabledCapability
                 + ", mConfigureGeoDetectionEnabledCapability="
                 + mConfigureGeoDetectionEnabledCapability
-                + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+                + ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
                 + '}';
     }
 
-    /** @hide */
+    /**
+     * A builder of {@link TimeZoneCapabilities} objects.
+     *
+     * @hide
+     */
     public static class Builder {
 
         @NonNull private UserHandle mUserHandle;
         private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
         private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
-        private @CapabilityState int mSuggestManualTimeZoneCapability;
+        private @CapabilityState int mSetManualTimeZoneCapability;
 
         public Builder(@NonNull UserHandle userHandle) {
             mUserHandle = Objects.requireNonNull(userHandle);
@@ -214,25 +215,27 @@
                 capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
             mConfigureGeoDetectionEnabledCapability =
                 capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
-            mSuggestManualTimeZoneCapability =
-                capabilitiesToCopy.mSuggestManualTimeZoneCapability;
+            mSetManualTimeZoneCapability =
+                capabilitiesToCopy.mSetManualTimeZoneCapability;
         }
 
-        /** Sets the state for the automatic time zone detection enabled config. */
+        /** Sets the value for the "configure automatic time zone detection enabled" capability. */
         public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
             this.mConfigureAutoDetectionEnabledCapability = value;
             return this;
         }
 
-        /** Sets the state for the geolocation time zone detection enabled config. */
+        /**
+         * Sets the value for the "configure geolocation time zone detection enabled" capability.
+         */
         public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
             this.mConfigureGeoDetectionEnabledCapability = value;
             return this;
         }
 
-        /** Sets the state for the suggestManualTimeZone action. */
-        public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
-            this.mSuggestManualTimeZoneCapability = value;
+        /** Sets the value for the "set manual time zone" capability. */
+        public Builder setSetManualTimeZoneCapability(@CapabilityState int value) {
+            this.mSetManualTimeZoneCapability = value;
             return this;
         }
 
@@ -243,8 +246,8 @@
                     "configureAutoDetectionEnabledCapability");
             verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
                     "configureGeoDetectionEnabledCapability");
-            verifyCapabilitySet(mSuggestManualTimeZoneCapability,
-                    "suggestManualTimeZoneCapability");
+            verifyCapabilitySet(mSetManualTimeZoneCapability,
+                    "mSetManualTimeZoneCapability");
             return new TimeZoneCapabilities(this);
         }
 
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/app/time/TimeZoneState.aidl
similarity index 75%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/app/time/TimeZoneState.aidl
index daebfbf..fc1962f 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/app/time/TimeZoneState.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.app.time;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable TimeZoneState;
diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java
new file mode 100644
index 0000000..8e87111
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneState.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A snapshot of the system's time zone state.
+ *
+ * <p>{@code id} contains the system's time zone ID setting, e.g. "America/Los_Angeles". This
+ * will usually agree with {@code TimeZone.getDefault().getID()} but it can be empty in rare cases.
+ *
+ * <p>{@code userShouldConfirmId} is {@code true} if the system has low confidence in the current
+ * time zone.
+ *
+ * @hide
+ */
+public final class TimeZoneState implements Parcelable {
+
+    public static final @NonNull Creator<TimeZoneState> CREATOR = new Creator<>() {
+        public TimeZoneState createFromParcel(Parcel in) {
+            return TimeZoneState.createFromParcel(in);
+        }
+
+        public TimeZoneState[] newArray(int size) {
+            return new TimeZoneState[size];
+        }
+    };
+
+    @NonNull private final String mId;
+    private final boolean mUserShouldConfirmId;
+
+    /** @hide */
+    public TimeZoneState(@NonNull String id, boolean userShouldConfirmId) {
+        mId = Objects.requireNonNull(id);
+        mUserShouldConfirmId = userShouldConfirmId;
+    }
+
+    private static TimeZoneState createFromParcel(Parcel in) {
+        String zoneId = in.readString8();
+        boolean userShouldConfirmId = in.readBoolean();
+        return new TimeZoneState(zoneId, userShouldConfirmId);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mId);
+        dest.writeBoolean(mUserShouldConfirmId);
+    }
+
+    /** @hide */
+    @Nullable
+    public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) {
+        String zoneIdString = null;
+        Boolean userShouldConfirmId = null;
+        String opt;
+        while ((opt = cmd.getNextArg()) != null) {
+            switch (opt) {
+                case "--zone_id": {
+                    zoneIdString  = cmd.getNextArgRequired();
+                    break;
+                }
+                case "--user_should_confirm_id": {
+                    userShouldConfirmId  = Boolean.parseBoolean(cmd.getNextArgRequired());
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+        if (zoneIdString == null) {
+            throw new IllegalArgumentException("No zoneId specified.");
+        }
+        if (userShouldConfirmId == null) {
+            throw new IllegalArgumentException("No userShouldConfirmId specified.");
+        }
+        return new TimeZoneState(zoneIdString, userShouldConfirmId);
+    }
+
+    /** @hide */
+    public static void printCommandLineOpts(@NonNull PrintWriter pw) {
+        pw.println("TimeZoneState options:");
+        pw.println("  --zone_id {<Olson ID>}");
+        pw.println("  --user_should_confirm_id {true|false}");
+        pw.println();
+        pw.println("See " + TimeZoneState.class.getName() + " for more information");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    public boolean getUserShouldConfirmId() {
+        return mUserShouldConfirmId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeZoneState that = (TimeZoneState) o;
+        return Objects.equals(mId, that.mId)
+                && mUserShouldConfirmId == that.mUserShouldConfirmId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mUserShouldConfirmId);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneState{"
+                + "mZoneId=" + mId
+                + ", mUserShouldConfirmId=" + mUserShouldConfirmId
+                + '}';
+    }
+}
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/app/time/UnixEpochTime.aidl
similarity index 82%
rename from core/java/android/app/cloudsearch/SearchResult.aidl
rename to core/java/android/app/time/UnixEpochTime.aidl
index daebfbf..3392e22 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/app/time/UnixEpochTime.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.app.time;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable UnixEpochTime;
diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java
new file mode 100644
index 0000000..576bf64
--- /dev/null
+++ b/core/java/android/app/time/UnixEpochTime.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.time;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * A Unix epoch time value with an associated reading from the elapsed realtime clock.
+ * When representing a device's system clock time, the Unix epoch time can be obtained using {@link
+ * System#currentTimeMillis()}. The Unix epoch time might also come from an external source
+ * depending on usage.
+ *
+ * <p>The elapsed realtime clock can be obtained using methods like {@link
+ * SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}.
+ *
+ * @hide
+ */
+public final class UnixEpochTime implements Parcelable {
+    @ElapsedRealtimeLong private final long mElapsedRealtimeMillis;
+    private final long mUnixEpochTimeMillis;
+
+    public UnixEpochTime(@ElapsedRealtimeLong long elapsedRealtimeMillis,
+            long unixEpochTimeMillis) {
+        mElapsedRealtimeMillis = elapsedRealtimeMillis;
+        mUnixEpochTimeMillis = unixEpochTimeMillis;
+    }
+
+    /** @hide */
+    @NonNull
+    public static UnixEpochTime parseCommandLineArgs(ShellCommand cmd) {
+        Long elapsedRealtimeMillis = null;
+        Long unixEpochTimeMillis = null;
+        String opt;
+        while ((opt = cmd.getNextArg()) != null) {
+            switch (opt) {
+                case "--elapsed_realtime": {
+                    elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                    break;
+                }
+                case "--unix_epoch_time": {
+                    unixEpochTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+
+        if (elapsedRealtimeMillis == null) {
+            throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
+        }
+        if (unixEpochTimeMillis == null) {
+            throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
+        }
+        return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+    }
+
+    /** @hide */
+    public static void printCommandLineOpts(PrintWriter pw) {
+        pw.println("UnixEpochTime options:\n");
+        pw.println("  --elapsed_realtime <elapsed realtime millis>");
+        pw.println("  --unix_epoch_time <Unix epoch time millis>");
+        pw.println();
+        pw.println("See " + UnixEpochTime.class.getName() + " for more information");
+    }
+
+    /** Returns the elapsed realtime clock value. See {@link UnixEpochTime} for more information. */
+    public @ElapsedRealtimeLong long getElapsedRealtimeMillis() {
+        return mElapsedRealtimeMillis;
+    }
+
+    /** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */
+    public long getUnixEpochTimeMillis() {
+        return mUnixEpochTimeMillis;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        UnixEpochTime that = (UnixEpochTime) o;
+        return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
+                && mUnixEpochTimeMillis == that.mUnixEpochTimeMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mElapsedRealtimeMillis, mUnixEpochTimeMillis);
+    }
+
+    @Override
+    public String toString() {
+        return "UnixEpochTime{"
+                + "mElapsedRealtimeTimeMillis=" + mElapsedRealtimeMillis
+                + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+                + '}';
+    }
+
+    public static final @NonNull Creator<UnixEpochTime> CREATOR = new Creator<>() {
+        @Override
+        public UnixEpochTime createFromParcel(@NonNull Parcel source) {
+            long elapsedRealtimeMillis = source.readLong();
+            long unixEpochTimeMillis = source.readLong();
+            return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+        }
+
+        @Override
+        public UnixEpochTime[] newArray(int size) {
+            return new UnixEpochTime[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mElapsedRealtimeMillis);
+        dest.writeLong(mUnixEpochTimeMillis);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
+     * Unix epoch time by the difference between the elapsed realtime value supplied and the one
+     * associated with this instance.
+     *
+     * @hide
+     */
+    public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) {
+        long adjustedUnixEpochTimeMillis =
+                (elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis;
+        return new UnixEpochTime(elapsedRealtimeTimeMillis, adjustedUnixEpochTimeMillis);
+    }
+
+    /**
+     * Returns the difference in milliseconds between two instance's elapsed realtimes.
+     *
+     * @hide
+     */
+    public static long elapsedRealtimeDifference(
+            @NonNull UnixEpochTime one, @NonNull UnixEpochTime two) {
+        return one.mElapsedRealtimeMillis - two.mElapsedRealtimeMillis;
+    }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 0eb2b54..a0c898e 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -20,20 +20,23 @@
 import android.app.time.ITimeDetectorListener;
 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.app.timedetector.TimePoint;
 
 /**
- * System private API to communicate with time detector service.
+ * Binder APIs to communicate with the time detector service.
  *
- * <p>Used by parts of the Android system with signals associated with the device's time to provide
- * information to the Time Detector Service.
+ * <p>Used to provide information to the Time Detector Service from other parts of the Android
+ * system that have access to time-related signals, e.g. telephony. Over time, System APIs have
+ * been added to support unbundled parts of the platform, e.g. SetUp Wizard.
  *
- * <p>Use the {@link android.app.timedetector.TimeDetector} class rather than going through
- * this Binder interface directly. See {@link android.app.timedetector.TimeDetectorService} for
- * more complete documentation.
- *
+ * <p>Use the {@link android.app.timedetector.TimeDetector} (internal API) and
+ * {@link android.app.time.TimeManager} (system API) classes rather than going through this Binder
+ * interface directly. See {@link android.app.timedetector.TimeDetectorService} for more complete
+ * documentation.
  *
  * {@hide}
  */
@@ -44,6 +47,10 @@
 
   boolean updateConfiguration(in TimeConfiguration timeConfiguration);
 
+  TimeState getTimeState();
+  boolean confirmTime(in UnixEpochTime time);
+  boolean setManualTime(in ManualTimeSuggestion timeZoneSuggestion);
+
   void suggestExternalTime(in ExternalTimeSuggestion timeSuggestion);
   boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
   void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index b447799..0be4267 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -18,10 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -51,7 +51,7 @@
 
     @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
 
-    public ManualTimeSuggestion(@NonNull TimestampedValue<Long> unixEpochTime) {
+    public ManualTimeSuggestion(@NonNull UnixEpochTime unixEpochTime) {
         mTimeSuggestionHelper = new TimeSuggestionHelper(ManualTimeSuggestion.class, unixEpochTime);
     }
 
@@ -70,7 +70,7 @@
     }
 
     @NonNull
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mTimeSuggestionHelper.getUnixEpochTime();
     }
 
diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index e0347c0..f149a26 100644
--- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -18,10 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -67,7 +67,7 @@
             };
 
     private final int mSlotIndex;
-    @Nullable private final TimestampedValue<Long> mUnixEpochTime;
+    @Nullable private final UnixEpochTime mUnixEpochTime;
     @Nullable private ArrayList<String> mDebugInfo;
 
     private TelephonyTimeSuggestion(Builder builder) {
@@ -78,13 +78,13 @@
 
     private static TelephonyTimeSuggestion createFromParcel(Parcel in) {
         int slotIndex = in.readInt();
-        TimestampedValue<Long> unixEpochTime =
-                in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
+        UnixEpochTime unixEpochTime =
+                in.readParcelable(null /* classLoader */, UnixEpochTime.class);
         TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUnixEpochTime(unixEpochTime)
                 .build();
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(
+        ArrayList<String> debugInfo = in.readArrayList(
                 null /* classLoader */, java.lang.String.class);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
@@ -96,7 +96,7 @@
     public static TelephonyTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
             throws IllegalArgumentException {
         Integer slotIndex = null;
-        Long referenceTimeMillis = null;
+        Long elapsedRealtimeMillis = null;
         Long unixEpochTimeMillis = null;
         String opt;
         while ((opt = cmd.getNextArg()) != null) {
@@ -105,8 +105,9 @@
                     slotIndex = Integer.parseInt(cmd.getNextArgRequired());
                     break;
                 }
-                case "--reference_time": {
-                    referenceTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                case "--reference_time":
+                case "--elapsed_realtime": {
+                    elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
                     break;
                 }
                 case "--unix_epoch_time": {
@@ -122,15 +123,14 @@
         if (slotIndex == null) {
             throw new IllegalArgumentException("No slotIndex specified.");
         }
-        if (referenceTimeMillis == null) {
-            throw new IllegalArgumentException("No referenceTimeMillis specified.");
+        if (elapsedRealtimeMillis == null) {
+            throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
         }
         if (unixEpochTimeMillis == null) {
             throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
         }
 
-        TimestampedValue<Long> timeSignal =
-                new TimestampedValue<>(referenceTimeMillis, unixEpochTimeMillis);
+        UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
         Builder builder = new Builder(slotIndex)
                 .setUnixEpochTime(timeSignal)
                 .addDebugInfo("Command line injection");
@@ -141,7 +141,7 @@
     public static void printCommandLineOpts(PrintWriter pw) {
         pw.println("Telephony suggestion options:");
         pw.println("  --slot_index <number>");
-        pw.println("  --reference_time <elapsed realtime millis>");
+        pw.println("  --elapsed_realtime <elapsed realtime millis>");
         pw.println("  --unix_epoch_time <Unix epoch time millis>");
         pw.println();
         pw.println("See " + TelephonyTimeSuggestion.class.getName() + " for more information");
@@ -174,7 +174,7 @@
      * <p>See {@link TelephonyTimeSuggestion} for more information about {@code unixEpochTime}.
      */
     @Nullable
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mUnixEpochTime;
     }
 
@@ -247,7 +247,7 @@
      */
     public static final class Builder {
         private final int mSlotIndex;
-        @Nullable private TimestampedValue<Long> mUnixEpochTime;
+        @Nullable private UnixEpochTime mUnixEpochTime;
         @Nullable private List<String> mDebugInfo;
 
         /**
@@ -265,12 +265,7 @@
          * <p>See {@link TelephonyTimeSuggestion} for more information about {@code unixEpochTime}.
          */
         @NonNull
-        public Builder setUnixEpochTime(@Nullable TimestampedValue<Long> unixEpochTime) {
-            if (unixEpochTime != null) {
-                // unixEpochTime can be null, but the value it holds cannot.
-                Objects.requireNonNull(unixEpochTime.getValue());
-            }
-
+        public Builder setUnixEpochTime(@Nullable UnixEpochTime unixEpochTime) {
             mUnixEpochTime = unixEpochTime;
             return this;
         }
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index db1614b..f95d6d3 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,9 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.app.time.UnixEpochTime;
 import android.content.Context;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 
 /**
  * The interface through which system components can query and send signals to the
@@ -85,14 +85,31 @@
     String SHELL_COMMAND_SUGGEST_EXTERNAL_TIME = "suggest_external_time";
 
     /**
+     * A shell command that retrieves the current system clock time state.
+     * @hide
+     */
+    String SHELL_COMMAND_GET_TIME_STATE = "get_time_state";
+
+    /**
+     * A shell command that sets the current time state for testing.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_TIME_STATE = "set_time_state_for_tests";
+
+    /**
+     * A shell command that sets the confidence in the current time state for testing.
+     * @hide
+     */
+    String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
+
+    /**
      * A shared utility method to create a {@link ManualTimeSuggestion}.
      *
      * @hide
      */
     static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
-        TimestampedValue<Long> utcTime =
-                new TimestampedValue<>(SystemClock.elapsedRealtime(), when);
-        ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(utcTime);
+        UnixEpochTime unixEpochTime = new UnixEpochTime(SystemClock.elapsedRealtime(), when);
+        ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(unixEpochTime);
         manualTimeSuggestion.addDebugInfo(why);
         return manualTimeSuggestion;
     }
diff --git a/core/java/android/app/timedetector/TimeSuggestionHelper.java b/core/java/android/app/timedetector/TimeSuggestionHelper.java
index e89839c..67dc6b8 100644
--- a/core/java/android/app/timedetector/TimeSuggestionHelper.java
+++ b/core/java/android/app/timedetector/TimeSuggestionHelper.java
@@ -18,9 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.os.Parcel;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -51,20 +51,19 @@
 public final class TimeSuggestionHelper {
 
     @NonNull private final Class<?> mHelpedClass;
-    @NonNull private final TimestampedValue<Long> mUnixEpochTime;
+    @NonNull private final UnixEpochTime mUnixEpochTime;
     @Nullable private ArrayList<String> mDebugInfo;
 
     /** Creates a helper for the specified class, containing the supplied properties. */
     public TimeSuggestionHelper(@NonNull Class<?> helpedClass,
-            @NonNull TimestampedValue<Long> unixEpochTime) {
+            @NonNull UnixEpochTime unixEpochTime) {
         mHelpedClass = Objects.requireNonNull(helpedClass);
         mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
-        Objects.requireNonNull(unixEpochTime.getValue());
     }
 
     /** See {@link TimeSuggestionHelper} for property details. */
     @NonNull
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mUnixEpochTime;
     }
 
@@ -146,8 +145,8 @@
     public static TimeSuggestionHelper handleCreateFromParcel(@NonNull Class<?> helpedClass,
             @NonNull Parcel in) {
         @SuppressWarnings("unchecked")
-        TimestampedValue<Long> unixEpochTime = in.readParcelable(
-                null /* classLoader */, TimestampedValue.class);
+        UnixEpochTime unixEpochTime =
+                in.readParcelable(null /* classLoader */, UnixEpochTime.class);
         TimeSuggestionHelper suggestionHelper =
                 new TimeSuggestionHelper(helpedClass, unixEpochTime);
         suggestionHelper.mDebugInfo = in.readArrayList(null /* classLoader */, String.class);
@@ -164,13 +163,14 @@
     public static TimeSuggestionHelper handleParseCommandLineArg(
             @NonNull Class<?> helpedClass, @NonNull ShellCommand cmd)
             throws IllegalArgumentException {
-        Long referenceTimeMillis = null;
+        Long elapsedRealtimeMillis = null;
         Long unixEpochTimeMillis = null;
         String opt;
         while ((opt = cmd.getNextArg()) != null) {
             switch (opt) {
-                case "--reference_time": {
-                    referenceTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                case "--reference_time":
+                case "--elapsed_realtime": {
+                    elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
                     break;
                 }
                 case "--unix_epoch_time": {
@@ -183,15 +183,14 @@
             }
         }
 
-        if (referenceTimeMillis == null) {
+        if (elapsedRealtimeMillis == null) {
             throw new IllegalArgumentException("No referenceTimeMillis specified.");
         }
         if (unixEpochTimeMillis == null) {
             throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
         }
 
-        TimestampedValue<Long> timeSignal =
-                new TimestampedValue<>(referenceTimeMillis, unixEpochTimeMillis);
+        UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
         TimeSuggestionHelper suggestionHelper = new TimeSuggestionHelper(helpedClass, timeSignal);
         suggestionHelper.addDebugInfo("Command line injection");
         return suggestionHelper;
@@ -201,8 +200,8 @@
     public static void handlePrintCommandLineOpts(
             @NonNull PrintWriter pw, @NonNull String typeName, @NonNull Class<?> clazz) {
         pw.printf("%s suggestion options:\n", typeName);
-        pw.println("  --reference_time <elapsed realtime millis> - the elapsed realtime millis when"
-                + " unix epoch time was read");
+        pw.println("  --elapsed_realtime <elapsed realtime millis> - the elapsed realtime millis"
+                + " when unix epoch time was read");
         pw.println("  --unix_epoch_time <Unix epoch time millis>");
         pw.println();
         pw.println("See " + clazz.getName() + " for more information");
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index af0389a..47d8e77 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -19,16 +19,19 @@
 import android.app.time.ITimeZoneDetectorListener;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 
 /**
- * System private API to communicate with time zone detector service.
+ * Binder APIs to communicate with time zone detector service.
  *
  * <p>Used to provide information to the Time Zone Detector Service from other parts of the Android
- * system that have access to time zone-related signals, e.g. telephony.
+ * system that have access to time zone-related signals, e.g. telephony. Over time, System APIs have
+ * been added to support unbundled parts of the platform, e.g. SetUp Wizard.
  *
- * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
+ * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} (internal API) and
+ * {@link android.app.time.TimeManager} (system API) classes rather than going through
  * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
  * for more complete documentation.
  *
@@ -41,6 +44,10 @@
 
   boolean updateConfiguration(in TimeZoneConfiguration configuration);
 
+  TimeZoneState getTimeZoneState();
+  boolean confirmTimeZone(in String timeZoneId);
+  boolean setManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
+
   boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
   void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index bae1c1c..0e9e28b 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -108,6 +108,24 @@
     String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
 
     /**
+     * A shell command that retrieves the current time zone setting state.
+     * @hide
+     */
+    String SHELL_COMMAND_GET_TIME_ZONE_STATE = "get_time_zone_state";
+
+    /**
+     * A shell command that sets the current time zone state for testing.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_TIME_ZONE_STATE = "set_time_zone_state_for_tests";
+
+    /**
+     * A shell command that sets the confidence in the current time zone state for testing.
+     * @hide
+     */
+    String SHELL_COMMAND_CONFIRM_TIME_ZONE = "confirm_time_zone";
+
+    /**
      * A shell command that dumps a {@link
      * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
      * debugging.
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 357bf59..4142bce 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -30,6 +30,7 @@
 import android.app.Activity;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
@@ -367,6 +368,10 @@
      * recommended to do when an association is no longer relevant to avoid unnecessary battery
      * and/or data drain resulting from special privileges that the association provides</p>
      *
+     * <p>Note that if you use this api to associate with a Bluetooth device, please make sure
+     * to cancel your own Bluetooth discovery before calling this api, otherwise the callback
+     * may fail to return the desired device.</p>
+     *
      * <p>Calling this API requires a uses-feature
      * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
      **
@@ -377,6 +382,7 @@
      * @see AssociationRequest.Builder
      * @see #getMyAssociations()
      * @see #disassociate(int)
+     * @see BluetoothAdapter#cancelDiscovery()
      */
     @UserHandleAware
     @RequiresPermission(anyOf = {
@@ -403,6 +409,34 @@
     }
 
     /**
+     * Cancel the current association activity.
+     *
+     * <p>The app should launch the returned {@code intentSender} by calling
+     * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to
+     * cancel the current association activity</p>
+     *
+     * <p>Calling this API requires a uses-feature
+     * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
+     *
+     * @return An {@link IntentSender} that the app should use to launch in order to cancel the
+     * current association activity
+     */
+    @UserHandleAware
+    @Nullable
+    public IntentSender buildAssociationCancellationIntent() {
+        if (!checkFeaturePresent()) return null;
+
+        try {
+            PendingIntent pendingIntent = mService.buildAssociationCancellationIntent(
+                    mContext.getOpPackageName(), mContext.getUserId());
+            return pendingIntent.getIntentSender();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * <p>Calling this API requires a uses-feature
      * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
      *
@@ -450,7 +484,8 @@
      * <p>Calling this API requires a uses-feature
      * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
      *
-     * @param deviceMacAddress the MAC address of device to disassociate from this app
+     * @param deviceMacAddress the MAC address of device to disassociate from this app. Device
+     * address is case-sensitive in API level &lt; 33.
      *
      * @deprecated use {@link #disassociate(int)}
      */
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 17e3132..24ef52b 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -82,4 +82,6 @@
     void detachSystemDataTransport(String packageName, int userId, int associationId);
 
     boolean isCompanionApplicationBound(String packageName, int userId);
+
+    PendingIntent buildAssociationCancellationIntent(in String callingPackage, int userId);
 }
diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS
index 004f66c..0348fe2 100644
--- a/core/java/android/companion/OWNERS
+++ b/core/java/android/companion/OWNERS
@@ -1,5 +1,3 @@
-ewol@google.com
 evanxinchen@google.com
 guojing@google.com
-svetoslavganov@google.com
-sergeynv@google.com
\ No newline at end of file
+raphk@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 9c99da5..e7f19166 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -43,6 +43,11 @@
     int getAssociationId();
 
     /**
+     * Returns the unique device ID for this virtual device.
+     */
+    int getDeviceId();
+
+    /**
      * Closes the virtual device and frees all associated resources.
      */
     void close();
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d4c9a42..08bee25 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -78,6 +78,16 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
 
+    /**
+     * The default device ID, which is the ID of the primary (non-virtual) device.
+     */
+    public static final int DEFAULT_DEVICE_ID = 0;
+
+    /**
+     * Invalid device ID.
+     */
+    public static final int INVALID_DEVICE_ID = -1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -204,6 +214,17 @@
         }
 
         /**
+         * Returns the unique ID of this virtual device.
+         */
+        public int getDeviceId() {
+            try {
+                return mVirtualDevice.getDeviceId();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Launches a given pending intent on the give display ID.
          *
          * @param displayId The display to launch the pending intent on. This display must be
@@ -445,8 +466,8 @@
                 @Nullable Executor executor,
                 @Nullable AudioConfigurationChangeCallback callback) {
             if (mVirtualAudioDevice == null) {
-                mVirtualAudioDevice = new VirtualAudioDevice(
-                        mContext, mVirtualDevice, display, executor, callback);
+                mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+                        executor, callback, () -> mVirtualAudioDevice = null);
             }
             return mVirtualAudioDevice;
         }
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
index 0db7b5f..e200a11 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -64,11 +64,24 @@
         void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
     }
 
+    /**
+     * Interface to be notified when {@link #close()} is called.
+     *
+     * @hide
+     */
+    public interface CloseListener {
+        /**
+         * Notifies when {@link #close()} is called.
+         */
+        void onClosed();
+    }
+
     private final Context mContext;
     private final IVirtualDevice mVirtualDevice;
     private final VirtualDisplay mVirtualDisplay;
     private final AudioConfigurationChangeCallback mCallback;
     private final Executor mExecutor;
+    private final CloseListener mListener;
     @Nullable
     private VirtualAudioSession mOngoingSession;
 
@@ -77,12 +90,13 @@
      */
     public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
             @NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
-            @Nullable AudioConfigurationChangeCallback callback) {
+            @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener) {
         mContext = context;
         mVirtualDevice = virtualDevice;
         mVirtualDisplay = virtualDisplay;
         mExecutor = executor;
         mCallback = callback;
+        mListener = listener;
     }
 
     /**
@@ -169,6 +183,10 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
+
+            if (mListener != null) {
+                mListener.onClosed();
+            }
         }
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 97da2da..cb5a99f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -51,11 +51,14 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionMethod;
+import android.content.pm.PermissionName;
 import android.content.res.AssetManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.credentials.CredentialManager;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -3932,6 +3935,7 @@
             //@hide: ATTESTATION_VERIFICATION_SERVICE,
             //@hide: SAFETY_CENTER_SERVICE,
             DISPLAY_HASH_SERVICE,
+            CREDENTIAL_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -6049,6 +6053,24 @@
     public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.healthconnect.HealthConnectManager}.
+     *
+     * @see #getSystemService(String)
+     * @see android.healthconnect.HealthConnectManager
+     */
+    public static final String HEALTHCONNECT_SERVICE = "healthconnect";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.credentials.CredentialManager} to authenticate a user to your app.
+     *
+     * @see #getSystemService(String)
+     * @see CredentialManager
+     */
+    public static final String CREDENTIAL_SERVICE = "credential";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6066,7 +6088,9 @@
      */
     @CheckResult(suggest="#enforcePermission(String,int,int,String)")
     @PackageManager.PermissionResult
-    public abstract int checkPermission(@NonNull String permission, int pid, int uid);
+    @PermissionMethod
+    public abstract int checkPermission(
+            @NonNull @PermissionName String permission, int pid, int uid);
 
     /** @hide */
     @SuppressWarnings("HiddenAbstractMethod")
@@ -6098,7 +6122,8 @@
      */
     @CheckResult(suggest="#enforceCallingPermission(String,String)")
     @PackageManager.PermissionResult
-    public abstract int checkCallingPermission(@NonNull String permission);
+    @PermissionMethod
+    public abstract int checkCallingPermission(@NonNull @PermissionName String permission);
 
     /**
      * Determine whether the calling process of an IPC <em>or you</em> have been
@@ -6118,7 +6143,8 @@
      */
     @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
     @PackageManager.PermissionResult
-    public abstract int checkCallingOrSelfPermission(@NonNull String permission);
+    @PermissionMethod
+    public abstract int checkCallingOrSelfPermission(@NonNull @PermissionName String permission);
 
     /**
      * Determine whether <em>you</em> have been granted a particular permission.
@@ -6146,8 +6172,9 @@
      *
      * @see #checkPermission(String, int, int)
      */
+    @PermissionMethod
     public abstract void enforcePermission(
-            @NonNull String permission, int pid, int uid, @Nullable String message);
+            @NonNull @PermissionName String permission, int pid, int uid, @Nullable String message);
 
     /**
      * If the calling process of an IPC you are handling has not been
@@ -6167,8 +6194,9 @@
      *
      * @see #checkCallingPermission(String)
      */
+    @PermissionMethod
     public abstract void enforceCallingPermission(
-            @NonNull String permission, @Nullable String message);
+            @NonNull @PermissionName String permission, @Nullable String message);
 
     /**
      * If neither you nor the calling process of an IPC you are
@@ -6183,8 +6211,9 @@
      *
      * @see #checkCallingOrSelfPermission(String)
      */
+    @PermissionMethod
     public abstract void enforceCallingOrSelfPermission(
-            @NonNull String permission, @Nullable String message);
+            @NonNull @PermissionName String permission, @Nullable String message);
 
     /**
      * Grant permission to access a specific Uri to another package, regardless
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index f9d3222..b95f8bb 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -1,11 +1,10 @@
 # Bug component: 36137
 
-toddke@android.com
-toddke@google.com
 patb@google.com
 
+per-file Package* = file:/PACKAGE_MANAGER_OWNERS
 per-file PackageParser.java = set noparent
-per-file PackageParser.java = chiuwinson@google.com,patb@google.com,toddke@google.com
+per-file PackageParser.java = chiuwinson@google.com,patb@google.com
 per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 270f870..833919e 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -18,3 +18,4 @@
 
 parcelable PackageInstaller.SessionParams;
 parcelable PackageInstaller.SessionInfo;
+parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5f45c342..5b18273 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -47,6 +47,7 @@
 import android.content.pm.PackageManager.InstallReason;
 import android.content.pm.PackageManager.InstallScenario;
 import android.graphics.Bitmap;
+import android.icu.util.ULocale;
 import android.net.Uri;
 import android.os.Build;
 import android.os.FileBridge;
@@ -65,6 +66,7 @@
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 
+import com.android.internal.util.DataClass;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -3275,4 +3277,280 @@
                     }
                 };
     }
+
+    /**
+     * Details for requesting the pre-commit install approval.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true, genBuilder = true,
+            genToString = true)
+    public static final class PreapprovalDetails implements Parcelable {
+        /**
+         * The icon representing the app to be installed.
+         */
+        private final @Nullable Bitmap mIcon;
+        /**
+         * The label representing the app to be installed.
+         */
+        private final @NonNull String mLabel;
+        /**
+         * The locale of the app label being used.
+         */
+        private final @NonNull ULocale mLocale;
+        /**
+         * The package name of the app to be installed.
+         */
+        private final @NonNull String mPackageName;
+
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new PreapprovalDetails.
+         *
+         * @param icon
+         *   The icon representing the app to be installed.
+         * @param label
+         *   The label representing the app to be installed.
+         * @param locale
+         *   The locale of the app label being used.
+         * @param packageName
+         *   The package name of the app to be installed.
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public PreapprovalDetails(
+                @Nullable Bitmap icon,
+                @NonNull String label,
+                @NonNull ULocale locale,
+                @NonNull String packageName) {
+            this.mIcon = icon;
+            this.mLabel = label;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mLabel);
+            this.mLocale = locale;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mLocale);
+            this.mPackageName = packageName;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mPackageName);
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * The icon representing the app to be installed.
+         */
+        @DataClass.Generated.Member
+        public @Nullable Bitmap getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * The label representing the app to be installed.
+         */
+        @DataClass.Generated.Member
+        public @NonNull String getLabel() {
+            return mLabel;
+        }
+
+        /**
+         * The locale of the app label being used.
+         */
+        @DataClass.Generated.Member
+        public @NonNull ULocale getLocale() {
+            return mLocale;
+        }
+
+        /**
+         * The package name of the app to be installed.
+         */
+        @DataClass.Generated.Member
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public String toString() {
+            // You can override field toString logic by defining methods like:
+            // String fieldNameToString() { ... }
+
+            return "PreapprovalDetails { " +
+                    "icon = " + mIcon + ", " +
+                    "label = " + mLabel + ", " +
+                    "locale = " + mLocale + ", " +
+                    "packageName = " + mPackageName +
+            " }";
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mIcon != null) flg |= 0x1;
+            dest.writeByte(flg);
+            if (mIcon != null) mIcon.writeToParcel(dest, flags);
+            dest.writeString8(mLabel);
+            dest.writeString8(mLocale.toString());
+            dest.writeString8(mPackageName);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ PreapprovalDetails(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
+            String label = in.readString8();
+            ULocale locale = new ULocale(in.readString8());
+            String packageName = in.readString8();
+
+            this.mIcon = icon;
+            this.mLabel = label;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mLabel);
+            this.mLocale = locale;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mLocale);
+            this.mPackageName = packageName;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mPackageName);
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<PreapprovalDetails> CREATOR
+                = new Parcelable.Creator<PreapprovalDetails>() {
+            @Override
+            public PreapprovalDetails[] newArray(int size) {
+                return new PreapprovalDetails[size];
+            }
+
+            @Override
+            public PreapprovalDetails createFromParcel(@NonNull Parcel in) {
+                return new PreapprovalDetails(in);
+            }
+        };
+
+        /**
+         * A builder for {@link PreapprovalDetails}
+         */
+        @SuppressWarnings("WeakerAccess")
+        @DataClass.Generated.Member
+        public static final class Builder {
+
+            private @Nullable Bitmap mIcon;
+            private @NonNull String mLabel;
+            private @NonNull ULocale mLocale;
+            private @NonNull String mPackageName;
+
+            private long mBuilderFieldsSet = 0L;
+
+            /**
+             * Creates a new Builder.
+             */
+            public Builder() {}
+
+            /**
+             * The icon representing the app to be installed.
+             */
+            @DataClass.Generated.Member
+            public @NonNull Builder setIcon(@NonNull Bitmap value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x1;
+                mIcon = value;
+                return this;
+            }
+
+            /**
+             * The label representing the app to be installed.
+             */
+            @DataClass.Generated.Member
+            public @NonNull Builder setLabel(@NonNull String value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x2;
+                mLabel = value;
+                return this;
+            }
+
+            /**
+             * The locale of the app label being used.
+             */
+            @DataClass.Generated.Member
+            public @NonNull Builder setLocale(@NonNull ULocale value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x4;
+                mLocale = value;
+                return this;
+            }
+
+            /**
+             * The package name of the app to be installed.
+             */
+            @DataClass.Generated.Member
+            public @NonNull Builder setPackageName(@NonNull String value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x8;
+                mPackageName = value;
+                return this;
+            }
+
+            /** Builds the instance. This builder should not be touched after calling this! */
+            public @NonNull PreapprovalDetails build() {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x10; // Mark builder used
+
+                PreapprovalDetails o = new PreapprovalDetails(
+                        mIcon,
+                        mLabel,
+                        mLocale,
+                        mPackageName);
+                return o;
+            }
+
+            private void checkNotUsed() {
+                if ((mBuilderFieldsSet & 0x10) != 0) {
+                    throw new IllegalStateException(
+                            "This Builder should not be reused. Use a new Builder instance instead");
+                }
+            }
+        }
+
+        @DataClass.Generated(
+                time = 1664257135109L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.String mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e2a5ea..db991dc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4187,6 +4187,13 @@
     public static final String FEATURE_WINDOW_MAGNIFICATION =
             "android.software.window_magnification";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports retrieval of user credentials, via integration with credential providers.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CREDENTIALS = "android.software.credentials";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
new file mode 100644
index 0000000..ba97342
--- /dev/null
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Documents that the subject method's job is to look
+ * up whether the provided or calling uid/pid has the requested permission.
+ *
+ * <p>Methods should either return `void`, but potentially throw {@link SecurityException},
+ * or return {@link android.content.pm.PackageManager.PermissionResult} `int`.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface PermissionMethod {}
diff --git a/core/java/android/content/pm/PermissionName.java b/core/java/android/content/pm/PermissionName.java
new file mode 100644
index 0000000..719e13b
--- /dev/null
+++ b/core/java/android/content/pm/PermissionName.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static 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.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated {@link String} represents a permission name.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface PermissionName {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 10d6f2d..64fed63 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,7 +78,6 @@
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
-    private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     private static final String TAG_APPLICATION = "application";
     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_PROFILEABLE = "profileable";
@@ -103,7 +102,7 @@
     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
+            return parseClusterPackageLite(input, packageFile, flags);
         } else {
             return parseMonolithicPackageLite(input, packageFile, flags);
         }
@@ -137,38 +136,19 @@
     /**
      * Parse lightweight details about a directory of APKs.
      *
-     * @param packageDirOrApk is the folder that contains split apks for a regular app or the
-     *                        framework-res.apk for framwork-res splits (in which case the
-     *                        splits come in the <code>frameworkSplits</code> parameter)
+     * @param packageDir is the folder that contains split apks for a regular app
      */
     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
-            File packageDirOrApk, List<File> frameworkSplits, int flags) {
+            File packageDir, int flags) {
         final File[] files;
-        final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
-        if (parsingFrameworkSplits) {
-            if (ArrayUtils.isEmpty(frameworkSplits)) {
-                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                        "No packages found in split");
-            }
-            files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
-            // we also want to process the base apk so add it to the array
-            files[files.length - 1] = packageDirOrApk;
-        } else {
-            files = packageDirOrApk.listFiles();
-            if (ArrayUtils.isEmpty(files)) {
-                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                        "No packages found in split");
-            }
-            // Apk directory is directly nested under the current directory
-            if (files.length == 1 && files[0].isDirectory()) {
-                return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
-            }
+        files = packageDir.listFiles();
+        if (ArrayUtils.isEmpty(files)) {
+            return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                    "No packages found in split");
         }
-
-        if (parsingFrameworkSplits) {
-            // disable the flag for checking the certificates of the splits. We know they
-            // won't match, but we rely on the mainline apex to be safe if it was installed
-            flags = flags & ~PARSE_COLLECT_CERTIFICATES;
+        // Apk directory is directly nested under the current directory
+        if (files.length == 1 && files[0].isDirectory()) {
+            return parseClusterPackageLite(input, files[0], flags);
         }
 
         String packageName = null;
@@ -186,10 +166,6 @@
                     }
 
                     final ApkLite lite = result.getResult();
-                    if (parsingFrameworkSplits && file == files[files.length - 1]) {
-                        baseApk = lite;
-                        break;
-                    }
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
@@ -201,8 +177,7 @@
                                     "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        // we allow version codes that do not match for framework splits
-                        if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
+                        if (versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                     "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
@@ -217,15 +192,11 @@
                     }
                 }
             }
-            // baseApk is set in the last iteration of the for each loop when we are parsing
-            // frameworkRes splits or needs to be done now otherwise
-            if (!parsingFrameworkSplits) {
-                baseApk = apks.remove(null);
-            }
+            baseApk = apks.remove(null);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
-        return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
+        return composePackageLiteFromApks(input, packageDir, baseApk, apks);
     }
 
     /**
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index 13f0b72..d51f64c 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import android.app.AppProtoEnums;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -30,6 +31,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
@@ -111,6 +113,14 @@
     private static Config sConfig;
 
     /**
+     * This array contains the statsd enum associated with each timer entry.  A value of NONE (0)
+     * means that the entry should not be logged to statsd.  (This would be the case for timers
+     * that are created for temporary debugging.)
+     */
+    @GuardedBy("sLock")
+    private static int[] sApiMap;
+
+    /**
      * A singleton Summary object that is refilled from the native side.  The length of the array
      * is the number of timers that can be fetched.  nativeGetTimers() will fill the array to the
      * smaller of the length of the array or the actual number of timers in the runtime.  The
@@ -165,6 +175,19 @@
                 sTimers[i].percentile = new int[sConfig.maxBuckets];
                 sTimers[i].largest = new int[sConfig.maxLargest];
             }
+            // Map the values returned from the runtime to statsd enumerals  The runtime may
+            // return timers that are not meant to be logged via statsd.  Such timers are mapped
+            // to RESOURCE_API_NONE.
+            sApiMap = new int[sConfig.maxTimer];
+            for (int i = 0; i < sApiMap.length; i++) {
+                if (sConfig.timers[i].equals("GetResourceValue")) {
+                    sApiMap[i] = AppProtoEnums.RESOURCE_API_GET_VALUE;
+                } else if (sConfig.timers[i].equals("RetrieveAttributes")) {
+                    sApiMap[i] = AppProtoEnums.RESOURCE_API_RETRIEVE_ATTRIBUTES;
+                } else {
+                    sApiMap[i] = AppProtoEnums.RESOURCE_API_NONE;
+                }
+            }
 
             sCurrentPoint = 0;
             startTimer();
@@ -194,7 +217,9 @@
             delay = sPublicationPoints[sCurrentPoint];
         } else {
             // Repeat with the final publication point.
-            delay = sCurrentPoint * sPublicationPoints[sPublicationPoints.length - 1];
+            final long repeated = sPublicationPoints[sPublicationPoints.length - 1];
+            final int prelude = sPublicationPoints.length - 1;
+            delay = (sCurrentPoint - prelude) * repeated;
         }
         // Convert minutes to milliseconds.
         delay *= 60 * 1000;
@@ -223,10 +248,19 @@
         update(true);
         // Log the number of records read.  This happens a few times a day.
         for (int i = 0; i < sTimers.length; i++) {
-            if (sTimers[i].count > 0) {
+            var timer = sTimers[i];
+            if (timer.count > 0) {
                 Log.i(TAG, TextUtils.formatSimple("%s count=%d pvalues=%s",
-                                sConfig.timers[i], sTimers[i].count,
-                                packedString(sTimers[i].percentile)));
+                                sConfig.timers[i], timer.count, packedString(timer.percentile)));
+                if (sApiMap[i] != AppProtoEnums.RESOURCE_API_NONE) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_API_INFO,
+                            sApiMap[i],
+                            timer.count, timer.total,
+                            timer.percentile[0], timer.percentile[1],
+                            timer.percentile[2], timer.percentile[3],
+                            timer.largest[0], timer.largest[1], timer.largest[2],
+                            timer.largest[3], timer.largest[4]);
+                }
             }
         }
         sCurrentPoint++;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 53dfed3..3915a6c 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -17,6 +17,7 @@
 package android.content.res;
 
 import static android.content.res.Resources.ID_NULL;
+import static android.system.OsConstants.EINVAL;
 
 import android.annotation.AnyRes;
 import android.annotation.NonNull;
@@ -28,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 
+import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -92,6 +94,16 @@
         }
     }
 
+    /**
+     * Reference Error.h UNEXPECTED_NULL
+     */
+    private static final int ERROR_NULL_DOCUMENT = Integer.MIN_VALUE + 8;
+    /**
+     * The reason not to ResXMLParser::BAD_DOCUMENT which is -1 is that other places use the same
+     * value. Reference Error.h BAD_VALUE = -EINVAL
+     */
+    private static final int ERROR_BAD_DOCUMENT = -EINVAL;
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public final class Parser implements XmlResourceParser {
         Parser(long parseState, XmlBlock block) {
@@ -168,7 +180,11 @@
             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
         }
         public int getLineNumber() {
-            return nativeGetLineNumber(mParseState);
+            final int lineNumber = nativeGetLineNumber(mParseState);
+            if (lineNumber == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
+            return lineNumber;
         }
         public int getEventType() throws XmlPullParserException {
             return mEventType;
@@ -203,7 +219,10 @@
         }
         @NonNull
         public String getAttributeNamespace(int index) {
-            int id = nativeGetAttributeNamespace(mParseState, index);
+            final int id = nativeGetAttributeNamespace(mParseState, index);
+            if (id == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
             else if (id == -1) return "";
@@ -211,8 +230,11 @@
         }
         @NonNull
         public String getAttributeName(int index) {
-            int id = nativeGetAttributeName(mParseState, index);
+            final int id = nativeGetAttributeName(mParseState, index);
             if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
+            if (id == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
             throw new IndexOutOfBoundsException(String.valueOf(index));
         }
@@ -224,21 +246,38 @@
             return false;
         }
         public int getAttributeCount() {
-            return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1;
+            if (mEventType == START_TAG) {
+                final int count = nativeGetAttributeCount(mParseState);
+                if (count == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return count;
+            } else {
+                return -1;
+            }
         }
         @NonNull
         public String getAttributeValue(int index) {
-            int id = nativeGetAttributeStringValue(mParseState, index);
+            final int id = nativeGetAttributeStringValue(mParseState, index);
+            if (id == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
 
             // May be some other type...  check and try to convert if so.
-            int t = nativeGetAttributeDataType(mParseState, index);
+            final int t = nativeGetAttributeDataType(mParseState, index);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             if (t == TypedValue.TYPE_NULL) {
                 throw new IndexOutOfBoundsException(String.valueOf(index));
             }
 
-            int v = nativeGetAttributeData(mParseState, index);
+            final int v = nativeGetAttributeData(mParseState, index);
+            if (v == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             return TypedValue.coerceToString(t, v);
         }
         public String getAttributeType(int index) {
@@ -272,6 +311,9 @@
                 return END_DOCUMENT;
             }
             int ev = nativeNext(mParseState);
+            if (ev == ERROR_BAD_DOCUMENT) {
+                throw new XmlPullParserException("Corrupt XML binary file");
+            }
             if (mDecNextDepth) {
                 mDepth--;
                 mDecNextDepth = false;
@@ -338,7 +380,11 @@
         }
     
         public int getAttributeNameResource(int index) {
-            return nativeGetAttributeResource(mParseState, index);
+            final int resourceNameId = nativeGetAttributeResource(mParseState, index);
+            if (resourceNameId == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
+            return resourceNameId;
         }
     
         public int getAttributeListValue(String namespace, String attribute,
@@ -393,8 +439,14 @@
 
         public int getAttributeListValue(int idx,
                 String[] options, int defaultValue) {
-            int t = nativeGetAttributeDataType(mParseState, idx);
-            int v = nativeGetAttributeData(mParseState, idx);
+            final int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
+            final int v = nativeGetAttributeData(mParseState, idx);
+            if (v == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             if (t == TypedValue.TYPE_STRING) {
                 return XmlUtils.convertValueToList(
                     mStrings.getSequence(v), options, defaultValue);
@@ -403,62 +455,99 @@
         }
         public boolean getAttributeBooleanValue(int idx,
                 boolean defaultValue) {
-            int t = nativeGetAttributeDataType(mParseState, idx);
+            final int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             // Note: don't attempt to convert any other types, because
             // we want to count on aapt doing the conversion for us.
-            if (t >= TypedValue.TYPE_FIRST_INT &&
-                t <= TypedValue.TYPE_LAST_INT) {
-                return nativeGetAttributeData(mParseState, idx) != 0;
+            if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
+                final int v = nativeGetAttributeData(mParseState, idx);
+                if (v == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return v != 0;
             }
             return defaultValue;
         }
         public int getAttributeResourceValue(int idx, int defaultValue) {
-            int t = nativeGetAttributeDataType(mParseState, idx);
+            final int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             // Note: don't attempt to convert any other types, because
             // we want to count on aapt doing the conversion for us.
             if (t == TypedValue.TYPE_REFERENCE) {
-                return nativeGetAttributeData(mParseState, idx);
+                final int v = nativeGetAttributeData(mParseState, idx);
+                if (v == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return v;
             }
             return defaultValue;
         }
         public int getAttributeIntValue(int idx, int defaultValue) {
-            int t = nativeGetAttributeDataType(mParseState, idx);
+            final int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             // Note: don't attempt to convert any other types, because
             // we want to count on aapt doing the conversion for us.
-            if (t >= TypedValue.TYPE_FIRST_INT &&
-                t <= TypedValue.TYPE_LAST_INT) {
-                return nativeGetAttributeData(mParseState, idx);
+            if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
+                final int v = nativeGetAttributeData(mParseState, idx);
+                if (v == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return v;
             }
             return defaultValue;
         }
         public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
             int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             // Note: don't attempt to convert any other types, because
             // we want to count on aapt doing the conversion for us.
-            if (t >= TypedValue.TYPE_FIRST_INT &&
-                t <= TypedValue.TYPE_LAST_INT) {
-                return nativeGetAttributeData(mParseState, idx);
+            if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
+                final int v = nativeGetAttributeData(mParseState, idx);
+                if (v == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return v;
             }
             return defaultValue;
         }
         public float getAttributeFloatValue(int idx, float defaultValue) {
-            int t = nativeGetAttributeDataType(mParseState, idx);
+            final int t = nativeGetAttributeDataType(mParseState, idx);
+            if (t == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             // Note: don't attempt to convert any other types, because
             // we want to count on aapt doing the conversion for us.
             if (t == TypedValue.TYPE_FLOAT) {
-                return Float.intBitsToFloat(
-                    nativeGetAttributeData(mParseState, idx));
+                final int v = nativeGetAttributeData(mParseState, idx);
+                if (v == ERROR_NULL_DOCUMENT) {
+                    throw new NullPointerException("Null document");
+                }
+                return Float.intBitsToFloat(v);
             }
             throw new RuntimeException("not a float!");
         }
         @Nullable
         public String getIdAttribute() {
-            int id = nativeGetIdAttribute(mParseState);
+            final int id = nativeGetIdAttribute(mParseState);
+            if (id == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
         }
         @Nullable
         public String getClassAttribute() {
-            int id = nativeGetClassAttribute(mParseState);
+            final int id = nativeGetClassAttribute(mParseState);
+            if (id == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
         }
 
@@ -468,7 +557,11 @@
         }
 
         public int getStyleAttribute() {
-            return nativeGetStyleAttribute(mParseState);
+            final int styleAttributeId = nativeGetStyleAttribute(mParseState);
+            if (styleAttributeId == ERROR_NULL_DOCUMENT) {
+                throw new NullPointerException("Null document");
+            }
+            return styleAttributeId;
         }
 
         private String getSequenceString(@Nullable CharSequence str) {
@@ -544,37 +637,55 @@
     // ----------- @FastNative ------------------
 
     @FastNative
+    private static native int nativeGetAttributeIndex(
+            long state, String namespace, String name);
+
+    // ----------- @CriticalNative ------------------
+    @CriticalNative
     /*package*/ static final native int nativeNext(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetNamespace(long state);
-    @FastNative
+
+    @CriticalNative
     /*package*/ static final native int nativeGetName(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetText(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetLineNumber(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeCount(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeNamespace(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeName(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeResource(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeDataType(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeData(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetAttributeStringValue(long state, int idx);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetIdAttribute(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetClassAttribute(long state);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetStyleAttribute(long state);
-    @FastNative
-    private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
-    @FastNative
+
+    @CriticalNative
     private static final native int nativeGetSourceResId(long state);
 }
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/credentials/CreateCredentialRequest.aidl
similarity index 74%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/credentials/CreateCredentialRequest.aidl
index daebfbf..5ab0b48 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/credentials/CreateCredentialRequest.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.credentials;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
new file mode 100644
index 0000000..22ef230
--- /dev/null
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+/**
+ * A request to register a specific type of user credential, potentially launching UI flows to
+ * collect user consent and any other operation needed.
+ */
+public final class CreateCredentialRequest implements Parcelable {
+
+    /**
+     * The requested credential type.
+     */
+    @NonNull
+    private final String mType;
+
+    /**
+     * The request data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
+     * Determines whether or not the request must only be fulfilled by a system provider.
+     */
+    private final boolean mRequireSystemProvider;
+
+    /**
+     * Returns the requested credential type.
+     */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the request data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    /**
+     * Returns true if the request must only be fulfilled by a system provider, and false
+     * otherwise.
+     *
+     * @hide
+     */
+    public boolean requireSystemProvider() {
+        return mRequireSystemProvider;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+        dest.writeBoolean(mRequireSystemProvider);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "CreateCredentialRequest {"
+                + "type=" + mType
+                + ", data=" + mData
+                + ", requireSystemProvider=" + mRequireSystemProvider
+                + "}";
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialRequest}.
+     *
+     * @param type the requested credential type
+     * @param data the request data
+     *
+     * @throws IllegalArgumentException If type is empty
+     */
+    public CreateCredentialRequest(@NonNull String type, @NonNull Bundle data) {
+        this(type, data, /*requireSystemProvider=*/ false);
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialRequest}.
+     *
+     * @param type the requested credential type
+     * @param data the request data
+     * @param requireSystemProvider whether or not the request must only be fulfilled by a system
+     *                              provider
+     *
+     * @throws IllegalArgumentException If type is empty.
+     *
+     * @hide
+     */
+    public CreateCredentialRequest(
+            @NonNull String type,
+            @NonNull Bundle data,
+            boolean requireSystemProvider) {
+        mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+        mData = requireNonNull(data, "data must not be null");
+        mRequireSystemProvider = requireSystemProvider;
+    }
+
+    private CreateCredentialRequest(@NonNull Parcel in) {
+        String type = in.readString8();
+        Bundle data = in.readBundle();
+        boolean requireSystemProvider = in.readBoolean();
+
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+        mRequireSystemProvider = requireSystemProvider;
+    }
+
+    public static final @NonNull Parcelable.Creator<CreateCredentialRequest> CREATOR =
+            new Parcelable.Creator<CreateCredentialRequest>() {
+        @Override
+        public CreateCredentialRequest[] newArray(int size) {
+            return new CreateCredentialRequest[size];
+        }
+
+        @Override
+        public CreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+            return new CreateCredentialRequest(in);
+        }
+    };
+}
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/credentials/CreateCredentialResponse.aidl
similarity index 74%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/credentials/CreateCredentialResponse.aidl
index daebfbf..83e1bc4 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/credentials/CreateCredentialResponse.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.credentials;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable CreateCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/CreateCredentialResponse.java b/core/java/android/credentials/CreateCredentialResponse.java
new file mode 100644
index 0000000..a3ee677
--- /dev/null
+++ b/core/java/android/credentials/CreateCredentialResponse.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A response object that encapsulates the result of a successful credential creation execution.
+ */
+public final class CreateCredentialResponse implements Parcelable {
+
+    /**
+     * The response data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
+     * Returns the response data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBundle(mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "CreateCredentialResponse {data=" + mData + "}";
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialResponse}.
+     *
+     * @param data the data associated with the credential created.
+     */
+    public CreateCredentialResponse(@NonNull Bundle data) {
+        mData = requireNonNull(data, "data must not be null");
+    }
+
+    private CreateCredentialResponse(@NonNull Parcel in) {
+        Bundle data = in.readBundle();
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+    }
+
+    public static final @NonNull Parcelable.Creator<CreateCredentialResponse> CREATOR =
+            new Parcelable.Creator<CreateCredentialResponse>() {
+        @Override
+        public CreateCredentialResponse[] newArray(int size) {
+            return new CreateCredentialResponse[size];
+        }
+
+        @Override
+        public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+            return new CreateCredentialResponse(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/Credential.java b/core/java/android/credentials/Credential.java
new file mode 100644
index 0000000..a247d16
--- /dev/null
+++ b/core/java/android/credentials/Credential.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents a user credential that can be used to authenticate to your app.
+ */
+public final class Credential implements Parcelable {
+
+    /**
+     * The credential type.
+     */
+    @NonNull
+    private final String mType;
+
+    /**
+     * The credential data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
+     * Returns the credential type.
+     */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the credential data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "Credential {" + "type=" + mType + ", data=" + mData + "}";
+    }
+
+    /**
+     * Constructs a {@link Credential}.
+     *
+     * @param type the type of the credential returned.
+     * @param data the data associated with the credential returned.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public Credential(@NonNull String type, @NonNull Bundle data) {
+        mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+        mData = requireNonNull(data, "data must not be null");
+    }
+
+    private Credential(@NonNull Parcel in) {
+        String type = in.readString8();
+        Bundle data = in.readBundle();
+
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+    }
+
+    public static final @NonNull Parcelable.Creator<Credential> CREATOR =
+            new Parcelable.Creator<Credential>() {
+        @Override
+        public Credential[] newArray(int size) {
+            return new Credential[size];
+        }
+
+        @Override
+        public Credential createFromParcel(@NonNull Parcel in) {
+            return new Credential(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
new file mode 100644
index 0000000..b9cef0f
--- /dev/null
+++ b/core/java/android/credentials/CredentialManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Manages user authentication flows.
+ *
+ * <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
+ * calling these framework apis.
+ *
+ * <p>The CredentialManager apis launch framework UI flows for a user to
+ * register a new credential or to consent to a saved credential from supported credential
+ * providers, which can then be used to authenticate to the app.
+ */
+@SystemService(Context.CREDENTIAL_SERVICE)
+public final class CredentialManager {
+    private static final String TAG = "CredentialManager";
+
+    private final Context mContext;
+    private final ICredentialManager mService;
+
+    /**
+     * @hide instantiated by ContextImpl.
+     */
+    public CredentialManager(Context context, ICredentialManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Launches the necessary flows to retrieve an app credential from the user.
+     *
+     * <p>The execution can potentially launch UI flows to collect user consent to using a
+     * credential, display a picker when multiple credentials exist, etc.
+     *
+     * @param request the request specifying type(s) of credentials to get from the user.
+     * @param cancellationSignal an optional signal that allows for cancelling this call.
+     * @param executor the callback will take place on this {@link Executor}.
+     * @param callback the callback invoked when the request succeeds or fails.
+     */
+    public void executeGetCredential(
+            @NonNull GetCredentialRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<
+                    GetCredentialResponse, CredentialManagerException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeGetCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.executeGetCredential(request,
+                    new GetCredentialTransport(executor, callback));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
+     * Launches the necessary flows to register an app credential for the user.
+     *
+     * <p>The execution can potentially launch UI flows to collect user consent to creating
+     * or storing the new credential, etc.
+     *
+     * @param request the request specifying type(s) of credentials to get from the user.
+     * @param cancellationSignal an optional signal that allows for cancelling this call.
+     * @param executor the callback will take place on this {@link Executor}.
+     * @param callback the callback invoked when the request succeeds or fails.
+     */
+    public void executeCreateCredential(
+            @NonNull CreateCredentialRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<
+                    CreateCredentialResponse, CredentialManagerException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.executeCreateCredential(request,
+                    new CreateCredentialTransport(executor, callback));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<
+                GetCredentialResponse, CredentialManagerException> mCallback;
+
+        private GetCredentialTransport(Executor executor,
+                OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(GetCredentialResponse response) {
+            mExecutor.execute(() -> mCallback.onResult(response));
+        }
+
+        @Override
+        public void onError(int errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+        }
+    }
+
+    private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<
+                CreateCredentialResponse, CredentialManagerException> mCallback;
+
+        private CreateCredentialTransport(Executor executor,
+                OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(CreateCredentialResponse response) {
+            mExecutor.execute(() -> mCallback.onResult(response));
+        }
+
+        @Override
+        public void onError(int errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+        }
+    }
+}
diff --git a/core/java/android/credentials/CredentialManagerException.java b/core/java/android/credentials/CredentialManagerException.java
new file mode 100644
index 0000000..8369649
--- /dev/null
+++ b/core/java/android/credentials/CredentialManagerException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.Nullable;
+
+/** Exception class for CredentialManager operations. */
+public class CredentialManagerException extends Exception {
+    /** Indicates that an unknown error was encountered. */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * The given CredentialManager operation is cancelled by the user.
+     *
+     * @hide
+     */
+    public static final int ERROR_USER_CANCELLED = 1;
+
+    /**
+     * No appropriate provider is found to support the target credential type(s).
+     *
+     * @hide
+     */
+    public static final int ERROR_PROVIDER_NOT_FOUND = 2;
+
+    public final int errorCode;
+
+    public CredentialManagerException(int errorCode, @Nullable String message) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public CredentialManagerException(
+            int errorCode, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorCode = errorCode;
+    }
+
+    public CredentialManagerException(int errorCode, @Nullable Throwable cause) {
+        super(cause);
+        this.errorCode = errorCode;
+    }
+
+    public CredentialManagerException(int errorCode) {
+        super();
+        this.errorCode = errorCode;
+    }
+}
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
new file mode 100644
index 0000000..a0d3c0b
--- /dev/null
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+/**
+ * A specific type of credential request.
+ */
+public final class GetCredentialOption implements Parcelable {
+
+    /**
+     * The requested credential type.
+     */
+    @NonNull
+    private final String mType;
+
+    /**
+     * The request data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
+     * Determines whether or not the request must only be fulfilled by a system provider.
+     */
+    private final boolean mRequireSystemProvider;
+
+    /**
+     * Returns the requested credential type.
+     */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the request data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    /**
+     * Returns true if the request must only be fulfilled by a system provider, and false
+     * otherwise.
+     *
+     * @hide
+     */
+    public boolean requireSystemProvider() {
+        return mRequireSystemProvider;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+        dest.writeBoolean(mRequireSystemProvider);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialOption {"
+                + "type=" + mType
+                + ", data=" + mData
+                + ", requireSystemProvider=" + mRequireSystemProvider
+                + "}";
+    }
+
+    /**
+     * Constructs a {@link GetCredentialOption}.
+     *
+     * @param type the requested credential type
+     * @param data the request data
+     *
+     * @throws IllegalArgumentException If type is empty
+     */
+    public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
+        this(type, data, /*requireSystemProvider=*/ false);
+    }
+
+    /**
+     * Constructs a {@link GetCredentialOption}.
+     *
+     * @param type the requested credential type
+     * @param data the request data
+     * @param requireSystemProvider whether or not the request must only be fulfilled by a system
+     *                              provider
+     *
+     * @throws IllegalArgumentException If type is empty.
+     *
+     * @hide
+     */
+    public GetCredentialOption(
+            @NonNull String type,
+            @NonNull Bundle data,
+            boolean requireSystemProvider) {
+        mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+        mData = requireNonNull(data, "data must not be null");
+        mRequireSystemProvider = requireSystemProvider;
+    }
+
+    private GetCredentialOption(@NonNull Parcel in) {
+        String type = in.readString8();
+        Bundle data = in.readBundle();
+        boolean requireSystemProvider = in.readBoolean();
+
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+        mRequireSystemProvider = requireSystemProvider;
+    }
+
+    public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
+            new Parcelable.Creator<GetCredentialOption>() {
+        @Override
+        public GetCredentialOption[] newArray(int size) {
+            return new GetCredentialOption[size];
+        }
+
+        @Override
+        public GetCredentialOption createFromParcel(@NonNull Parcel in) {
+            return new GetCredentialOption(in);
+        }
+    };
+}
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/credentials/GetCredentialRequest.aidl
similarity index 75%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/credentials/GetCredentialRequest.aidl
index daebfbf..2632a60 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/credentials/GetCredentialRequest.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.credentials;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable GetCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
new file mode 100644
index 0000000..96a0ce1
--- /dev/null
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A request to retrieve the user credential, potentially launching UI flows to let the user pick
+ * from different credential sources.
+ */
+public final class GetCredentialRequest implements Parcelable {
+
+    /**
+     * The list of credential requests.
+     */
+    @NonNull
+    private final List<GetCredentialOption> mGetCredentialOptions;
+
+    /**
+     * Returns the list of credential options to be requested.
+     */
+    @NonNull
+    public List<GetCredentialOption> getGetCredentialOptions() {
+        return mGetCredentialOptions;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mGetCredentialOptions, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialRequest {getCredentialOption=" + mGetCredentialOptions + "}";
+    }
+
+    private GetCredentialRequest(@NonNull List<GetCredentialOption> getCredentialOptions) {
+        Preconditions.checkCollectionNotEmpty(
+                getCredentialOptions,
+                /*valueName=*/ "getCredentialOptions");
+        Preconditions.checkCollectionElementsNotNull(
+                getCredentialOptions,
+                /*valueName=*/ "getCredentialOptions");
+        mGetCredentialOptions = getCredentialOptions;
+    }
+
+    private GetCredentialRequest(@NonNull Parcel in) {
+        List<GetCredentialOption> getCredentialOptions = new ArrayList<GetCredentialOption>();
+        in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
+        mGetCredentialOptions = getCredentialOptions;
+        AnnotationValidations.validate(NonNull.class, null, mGetCredentialOptions);
+    }
+
+    public static final @NonNull Parcelable.Creator<GetCredentialRequest> CREATOR =
+            new Parcelable.Creator<GetCredentialRequest>() {
+        @Override
+        public GetCredentialRequest[] newArray(int size) {
+            return new GetCredentialRequest[size];
+        }
+
+        @Override
+        public GetCredentialRequest createFromParcel(@NonNull Parcel in) {
+            return new GetCredentialRequest(in);
+        }
+    };
+
+    /** A builder for {@link GetCredentialRequest}. */
+    public static final class Builder {
+
+        private @NonNull List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+
+        /**
+         * Adds a specific type of {@link GetCredentialOption}.
+         */
+        public @NonNull Builder addGetCredentialOption(
+                @NonNull GetCredentialOption getCredentialOption) {
+            mGetCredentialOptions.add(requireNonNull(
+                    getCredentialOption, "getCredentialOption must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets the list of {@link GetCredentialOption}.
+         */
+        public @NonNull Builder setGetCredentialOptions(
+                @NonNull List<GetCredentialOption> getCredentialOptions) {
+            Preconditions.checkCollectionElementsNotNull(
+                    getCredentialOptions,
+                    /*valueName=*/ "getCredentialOptions");
+            mGetCredentialOptions = new ArrayList<>(getCredentialOptions);
+            return this;
+        }
+
+        /**
+         * Builds a {@link GetCredentialRequest}.
+         *
+         * @throws IllegalArgumentException If getCredentialOptions is empty.
+         */
+        public @NonNull GetCredentialRequest build() {
+            Preconditions.checkCollectionNotEmpty(
+                    mGetCredentialOptions,
+                    /*valueName=*/ "getCredentialOptions");
+            Preconditions.checkCollectionElementsNotNull(
+                    mGetCredentialOptions,
+                    /*valueName=*/ "getCredentialOptions");
+            return new GetCredentialRequest(mGetCredentialOptions);
+        }
+    }
+}
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/credentials/GetCredentialResponse.aidl
similarity index 75%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/credentials/GetCredentialResponse.aidl
index daebfbf..71ebb12 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/credentials/GetCredentialResponse.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.credentials;
 
-parcelable SearchResult;
\ No newline at end of file
+parcelable GetCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetCredentialResponse.java b/core/java/android/credentials/GetCredentialResponse.java
new file mode 100644
index 0000000..576da8b
--- /dev/null
+++ b/core/java/android/credentials/GetCredentialResponse.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A response object that encapsulates the credential successfully retrieved from the user.
+ */
+public final class GetCredentialResponse implements Parcelable {
+
+    /**
+     * The credential that can be used to authenticate the user.
+     */
+    @Nullable
+    private final Credential mCredential;
+
+    /**
+     * Returns the credential that can be used to authenticate the user, or {@code null} if no
+     * credential is available.
+     */
+    @Nullable
+    public Credential getCredential() {
+        return mCredential;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mCredential, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialResponse {" + "credential=" + mCredential + "}";
+    }
+
+    /**
+     * Constructs a {@link GetCredentialResponse}.
+     *
+     * @param credential the credential successfully retrieved from the user.
+     */
+    public GetCredentialResponse(@NonNull Credential credential) {
+        mCredential = requireNonNull(credential, "credential must not be null");
+    }
+
+    /**
+     * Constructs a {@link GetCredentialResponse}.
+     */
+    public GetCredentialResponse() {
+        mCredential = null;
+    }
+
+    private GetCredentialResponse(@NonNull Parcel in) {
+        Credential credential = in.readTypedObject(Credential.CREATOR);
+        mCredential = credential;
+        AnnotationValidations.validate(NonNull.class, null, mCredential);
+    }
+
+    public static final @NonNull Parcelable.Creator<GetCredentialResponse> CREATOR =
+            new Parcelable.Creator<GetCredentialResponse>() {
+        @Override
+        public GetCredentialResponse[] newArray(int size) {
+            return new GetCredentialResponse[size];
+        }
+
+        @Override
+        public GetCredentialResponse createFromParcel(@NonNull Parcel in) {
+            return new GetCredentialResponse(in);
+        }
+    };
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl
similarity index 62%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to core/java/android/credentials/ICreateCredentialCallback.aidl
index 104bf99..75620fa 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/core/java/android/credentials/ICreateCredentialCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
+package android.credentials;
 
-import android.app.cloudsearch.SearchRequest;
+import android.credentials.CreateCredentialResponse;
 
 /**
- * Interface from the system to CloudSearch service.
+ * Listener for an executeCreateCredential request.
  *
  * @hide
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+interface ICreateCredentialCallback {
+    oneway void onResponse(in CreateCredentialResponse response);
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index a281cd5..dcf7106 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -1,10 +1,35 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.credentials;
 
+import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialRequest;
+import android.credentials.ICreateCredentialCallback;
+import android.credentials.IGetCredentialCallback;
+import android.os.ICancellationSignal;
+
 /**
- * Mediator between apps and credential manager service implementations.
+ * System private interface for talking to the credential manager service.
  *
- * {@hide}
+ * @hide
  */
-oneway interface ICredentialManager {
-    void getCredential();
-}
\ No newline at end of file
+interface ICredentialManager {
+
+    @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback);
+
+    @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl
similarity index 63%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to core/java/android/credentials/IGetCredentialCallback.aidl
index 104bf99..92e5851 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/core/java/android/credentials/IGetCredentialCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
+package android.credentials;
 
-import android.app.cloudsearch.SearchRequest;
+import android.credentials.GetCredentialResponse;
 
 /**
- * Interface from the system to CloudSearch service.
+ * Listener for an executeGetCredential request.
  *
  * @hide
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+interface IGetCredentialCallback {
+    oneway void onResponse(in GetCredentialResponse response);
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
new file mode 100644
index 0000000..49e5e49
--- /dev/null
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Holds metadata and credential entries for a single provider.
+ *
+ * @hide
+ */
+public class ProviderData implements Parcelable {
+
+    /**
+     * The intent extra key for the list of {@code ProviderData} when launching the UX
+     * activities.
+     */
+    public static final String EXTRA_PROVIDER_DATA_LIST =
+            "android.credentials.ui.extra.PROVIDER_DATA_LIST";
+
+    // TODO: add entry data.
+
+    @NonNull
+    private final String mPackageName;
+
+    public ProviderData(@NonNull String packageName) {
+        mPackageName = packageName;
+    }
+
+    /** Returns the provider package name. */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    protected ProviderData(@NonNull Parcel in) {
+        String packageName = in.readString8();
+        mPackageName = packageName;
+        AnnotationValidations.validate(NonNull.class, null, mPackageName);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mPackageName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<ProviderData> CREATOR = new Creator<ProviderData>() {
+        @Override
+        public ProviderData createFromParcel(@NonNull Parcel in) {
+            return new ProviderData(in);
+        }
+
+        @Override
+        public ProviderData[] newArray(int size) {
+            return new ProviderData[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
new file mode 100644
index 0000000..eddb519
--- /dev/null
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Contains information about the request that initiated this UX flow.
+ *
+ * @hide
+ */
+public class RequestInfo implements Parcelable {
+
+    /**
+     * The intent extra key for the {@code RequestInfo} object when launching the UX
+     * activities.
+     */
+    public static final @NonNull String EXTRA_REQUEST_INFO =
+            "android.credentials.ui.extra.REQUEST_INFO";
+
+    /** Type value for an executeGetCredential request. */
+    public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
+    /** Type value for an executeCreateCredential request. */
+    public static final @NonNull String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
+
+    @NonNull
+    private final IBinder mToken;
+
+    @NonNull
+    private final String mType;
+
+    private final boolean mIsFirstUsage;
+
+    public RequestInfo(@NonNull IBinder token, @NonNull String type, boolean isFirstUsage) {
+        mToken = token;
+        mType = type;
+        mIsFirstUsage = isFirstUsage;
+    }
+
+    /** Returns the request token matching the user request. */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    /** Returns the request type. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns whether this is the first Credential Manager usage for this user on the device.
+     *
+     * If true, the user will be prompted for a provider-centric dialog first to confirm their
+     * provider choices.
+     */
+    public boolean isFirstUsage() {
+        return mIsFirstUsage;
+    }
+
+    protected RequestInfo(@NonNull Parcel in) {
+        IBinder token = in.readStrongBinder();
+        String type = in.readString8();
+        boolean isFirstUsage = in.readBoolean();
+
+        mToken = token;
+        AnnotationValidations.validate(NonNull.class, null, mToken);
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mIsFirstUsage = isFirstUsage;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeString8(mType);
+        dest.writeBoolean(mIsFirstUsage);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
+        @Override
+        public RequestInfo createFromParcel(@NonNull Parcel in) {
+            return new RequestInfo(in);
+        }
+
+        @Override
+        public RequestInfo[] newArray(int size) {
+            return new RequestInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
new file mode 100644
index 0000000..0927fb8
--- /dev/null
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * User selection result information of a UX flow.
+ *
+ * Returned as part of the activity result intent data when the user dialog completes
+ * successfully.
+ *
+ * @hide
+ */
+public class UserSelectionResult implements Parcelable {
+
+    /**
+    * The intent extra key for the {@code UserSelectionResult} object when the credential selector
+    * activity finishes.
+    */
+    public static final String EXTRA_USER_SELECTION_RESULT =
+            "android.credentials.ui.extra.USER_SELECTION_RESULT";
+
+    @NonNull
+    private final IBinder mRequestToken;
+
+    // TODO: consider switching to string or other types, depending on the service implementation.
+    private final int mEntryId;
+
+    public UserSelectionResult(@NonNull IBinder requestToken, int entryId) {
+        mRequestToken = requestToken;
+        mEntryId = entryId;
+    }
+
+    /** Returns token of the app request that initiated this user dialog. */
+    @NonNull
+    public IBinder getRequestToken() {
+        return mRequestToken;
+    }
+
+    /** Returns the id of the visual entry that the user selected. */
+    public int geEntryId() {
+        return mEntryId;
+    }
+
+    protected UserSelectionResult(@NonNull Parcel in) {
+        IBinder requestToken = in.readStrongBinder();
+        int entryId = in.readInt();
+
+        mRequestToken = requestToken;
+        AnnotationValidations.validate(NonNull.class, null, mRequestToken);
+        mEntryId = entryId;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mRequestToken);
+        dest.writeInt(mEntryId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<UserSelectionResult> CREATOR =
+            new Creator<UserSelectionResult>() {
+        @Override
+        public UserSelectionResult createFromParcel(@NonNull Parcel in) {
+            return new UserSelectionResult(in);
+        }
+
+        @Override
+        public UserSelectionResult[] newArray(int size) {
+            return new UserSelectionResult[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 6c42776..15eae09 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -385,14 +385,6 @@
      */
     public static final int RANGE_EXTENDED = 3 << 27;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-        DATASPACE_DEPTH,
-        DATASPACE_DYNAMIC_DEPTH,
-    })
-    public @interface DataSpaceDepth {};
-
     /**
      * Depth.
      *
@@ -407,13 +399,6 @@
      */
     public static final int DATASPACE_DYNAMIC_DEPTH = 4098;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-        DATASPACE_HEIF,
-    })
-    public @interface DataSpaceFileFormat {};
-
     /**
      * High Efficiency Image File Format (HEIF).
      *
@@ -442,7 +427,7 @@
         DATASPACE_DCI_P3,
         DATASPACE_SRGB_LINEAR
     })
-    public @interface NamedDataSpace {};
+    public @interface ColorDataSpace {};
 
     /**
      * Default-assumption data space, when not explicitly specified.
@@ -635,6 +620,30 @@
      */
     public static final int DATASPACE_SRGB_LINEAR = 138477568;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+        DATASPACE_DEPTH,
+        DATASPACE_DYNAMIC_DEPTH,
+        DATASPACE_HEIF,
+        DATASPACE_UNKNOWN,
+        DATASPACE_SCRGB_LINEAR,
+        DATASPACE_SRGB,
+        DATASPACE_SCRGB,
+        DATASPACE_DISPLAY_P3,
+        DATASPACE_BT2020_HLG,
+        DATASPACE_BT2020_PQ,
+        DATASPACE_ADOBE_RGB,
+        DATASPACE_JFIF,
+        DATASPACE_BT601_625,
+        DATASPACE_BT601_525,
+        DATASPACE_BT2020,
+        DATASPACE_BT709,
+        DATASPACE_DCI_P3,
+        DATASPACE_SRGB_LINEAR
+    })
+    public @interface NamedDataSpace {};
+
     private DataSpace() {}
 
     /**
@@ -647,7 +656,7 @@
      *
      * @return The int dataspace packed by standard, transfer and range value
      */
-    public static @NamedDataSpace int pack(@DataSpaceStandard int standard,
+    public static @ColorDataSpace int pack(@DataSpaceStandard int standard,
                                         @DataSpaceTransfer int transfer,
                                         @DataSpaceRange int range) {
         if ((standard & STANDARD_MASK) != standard) {
@@ -669,7 +678,7 @@
      *
      * @return The standard aspect
      */
-    public static @DataSpaceStandard int getStandard(@NamedDataSpace int dataSpace) {
+    public static @DataSpaceStandard int getStandard(@ColorDataSpace int dataSpace) {
         @DataSpaceStandard int standard = dataSpace & STANDARD_MASK;
         return standard;
     }
@@ -681,7 +690,7 @@
      *
      * @return The transfer aspect
      */
-    public static @DataSpaceTransfer int getTransfer(@NamedDataSpace int dataSpace) {
+    public static @DataSpaceTransfer int getTransfer(@ColorDataSpace int dataSpace) {
         @DataSpaceTransfer int transfer = dataSpace & TRANSFER_MASK;
         return transfer;
     }
@@ -693,7 +702,7 @@
      *
      * @return The range aspect
      */
-    public static @DataSpaceRange int getRange(@NamedDataSpace int dataSpace) {
+    public static @DataSpaceRange int getRange(@ColorDataSpace int dataSpace) {
         @DataSpaceRange int range = dataSpace & RANGE_MASK;
         return range;
     }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d235f12..6c3233c 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -472,6 +472,10 @@
 
         @Override
         public void onCancel() {
+            if (!mIsPromptShowing) {
+                Log.w(TAG, "BP is not showing");
+                return;
+            }
             Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
             cancelAuthentication(mAuthRequestId);
         }
@@ -496,6 +500,7 @@
                 final AuthenticationResult result =
                         new AuthenticationResult(mCryptoObject, authenticationType);
                 mAuthenticationCallback.onAuthenticationSucceeded(result);
+                mIsPromptShowing = false;
             });
         }
 
@@ -503,6 +508,7 @@
         public void onAuthenticationFailed() {
             mExecutor.execute(() -> {
                 mAuthenticationCallback.onAuthenticationFailed();
+                mIsPromptShowing = false;
             });
         }
 
@@ -550,6 +556,7 @@
             final String stringToSend = errorMessage;
             mExecutor.execute(() -> {
                 mAuthenticationCallback.onAuthenticationError(error, stringToSend);
+                mIsPromptShowing = false;
             });
         }
 
@@ -566,8 +573,10 @@
             if (reason == DISMISSED_REASON_NEGATIVE) {
                 mNegativeButtonInfo.executor.execute(() -> {
                     mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+                    mIsPromptShowing = false;
                 });
             } else {
+                mIsPromptShowing = false;
                 Log.e(TAG, "Unknown reason: " + reason);
             }
         }
@@ -580,12 +589,15 @@
         }
     };
 
+    private boolean mIsPromptShowing;
+
     private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
         mContext = context;
         mPromptInfo = promptInfo;
         mNegativeButtonInfo = negativeButtonInfo;
         mService = IAuthService.Stub.asInterface(
                 ServiceManager.getService(Context.AUTH_SERVICE));
+        mIsPromptShowing = false;
     }
 
     /**
@@ -1095,7 +1107,6 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
             int userId) {
-
         // Ensure we don't return the wrong crypto object as an auth result.
         if (mCryptoObject != null && mCryptoObject.getOpId() != operationId) {
             Log.w(TAG, "CryptoObject operation ID does not match argument; setting field to null");
@@ -1110,6 +1121,14 @@
 
             mExecutor = executor;
             mAuthenticationCallback = callback;
+            if (mIsPromptShowing) {
+                final String stringToSend = mContext.getString(R.string.biometric_error_canceled);
+                mExecutor.execute(() -> {
+                    mAuthenticationCallback.onAuthenticationError(BIOMETRIC_ERROR_CANCELED,
+                            stringToSend);
+                });
+                return -1;
+            }
 
             final PromptInfo promptInfo;
             if (operationId != 0L) {
@@ -1131,6 +1150,8 @@
             final long authId = mService.authenticate(mToken, operationId, userId,
                     mBiometricServiceReceiver, mContext.getPackageName(), promptInfo);
             cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+            mIsPromptShowing = true;
+
             return authId;
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception while authenticating", e);
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index df1c0d7..10a7538 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -826,7 +826,9 @@
      * <p> Here, SC Map, refers to the {@link StreamConfigurationMap}, the target stream sizes must
      * be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link
      * StreamConfigurationMap} and {@code MAX_RES} refers to the maximum resolution {@link
-     * StreamConfigurationMap}. The same capture request must not mix targets from
+     * StreamConfigurationMap}. For {@code MAX_RES} streams, {@code MAX} in the {@code Max size} column refers to the maximum size from
+     * {@link StreamConfigurationMap#getOutputSizes} and {@link StreamConfigurationMap#getHighResolutionOutputSizes}.
+     * Note: The same capture request must not mix targets from
      * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. </p>
      *
      * <p> 10-bit output capable
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 26415d3..0905e1b 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1723,7 +1723,17 @@
                 }
 
                 if (isUltraHighResolution) {
-                    sizes.add(getMaxSize(sm.getOutputSizes(formatChosen)));
+                    Size [] outputSizes = sm.getOutputSizes(formatChosen);
+                    Size [] highResolutionOutputSizes =
+                            sm.getHighResolutionOutputSizes(formatChosen);
+                    Size maxBurstSize = getMaxSizeOrNull(outputSizes);
+                    Size maxHighResolutionSize = getMaxSizeOrNull(highResolutionOutputSizes);
+                    Size chosenMaxSize =
+                            maxBurstSize != null ? maxBurstSize : maxHighResolutionSize;
+                    if (maxBurstSize != null && maxHighResolutionSize != null) {
+                        chosenMaxSize = getMaxSize(maxBurstSize, maxHighResolutionSize);
+                    }
+                    sizes.add(chosenMaxSize);
                 } else {
                     if (formatChosen == ImageFormat.RAW_SENSOR) {
                         // RAW_SENSOR always has MAXIMUM threshold.
@@ -2126,6 +2136,21 @@
         }
 
         /**
+         * Get the largest size by area.
+         *
+         * @param sizes an array of sizes
+         *
+         * @return Largest Size or null if sizes was null or had 0 elements
+         */
+        public static @Nullable Size getMaxSizeOrNull(Size... sizes) {
+            if (sizes == null || sizes.length == 0) {
+                return null;
+            }
+
+            return getMaxSize(sizes);
+        }
+
+        /**
          * Whether or not the hardware level reported by android.info.supportedHardwareLevel is
          * at least the desired one (but could be higher)
          */
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 9e87037..90e92db 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -159,8 +159,9 @@
      *
      * <li> For a SurfaceView output surface, the timestamp base is {@link
      * #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}. The timestamp is overridden with choreographer
-     * pulses from the display subsystem for smoother display of camera frames. The timestamp
-     * is roughly in the same time base as {@link android.os.SystemClock#uptimeMillis}.</li>
+     * pulses from the display subsystem for smoother display of camera frames when the camera
+     * device runs in fixed frame rate. The timestamp is roughly in the same time base as
+     * {@link android.os.SystemClock#uptimeMillis}.</li>
      * <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
      * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
      * {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
@@ -231,7 +232,8 @@
      *
      * <p>The timestamp of the output images are overridden with choreographer pulses from the
      * display subsystem for smoother display of camera frames. An output target of SurfaceView
-     * uses this time base by default.</p>
+     * uses this time base by default. Note that the timestamp override is done for fixed camera
+     * frame rate only.</p>
      *
      * <p>This timestamp base isn't applicable to SurfaceTexture targets. SurfaceTexture's
      * {@link android.graphics.SurfaceTexture#updateTexImage updateTexImage} function always
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 30aa4db..bdd45e6 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware.devicestate;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -115,6 +116,52 @@
     }
 
     /**
+     * Submits a {@link DeviceStateRequest request} to override the base state of the device. This
+     * should only be used for testing, where you want to simulate the physical change to the
+     * device state.
+     * <p>
+     * By default, the request is kept active until one of the following occurs:
+     * <ul>
+     *     <li>The physical state of the device changes</li>
+     *     <li>The system deems the request can no longer be honored, for example if the requested
+     *     state becomes unsupported.
+     *     <li>A call to {@link #cancelBaseStateOverride}.
+     *     <li>Another processes submits a request succeeding this request in which case the request
+     *     will be canceled.
+     * </ul>
+     *
+     * Submitting a base state override request may not cause any change in the presentation
+     * of the system if there is an emulated request made through {@link #requestState}, as the
+     * emulated override requests take priority.
+     *
+     * @throws IllegalArgumentException if the requested state is unsupported.
+     * @throws SecurityException if the caller does not hold the
+     * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+     *
+     * @see DeviceStateRequest
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+            @Nullable @CallbackExecutor Executor executor,
+            @Nullable DeviceStateRequest.Callback callback) {
+        mGlobal.requestBaseStateOverride(request, executor, callback);
+    }
+
+    /**
+     * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
+     * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     * <p>
+     * This method is noop if there is no base state request currently active.
+     *
+     * @throws SecurityException if the caller does not hold the
+     * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE)
+    public void cancelBaseStateOverride() {
+        mGlobal.cancelBaseStateOverride();
+    }
+
+    /**
      * Registers a callback to receive notifications about changes in device state.
      *
      * @param executor the executor to process notifications.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index aba538f..738045d 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.os.Binder;
@@ -81,6 +82,7 @@
     @VisibleForTesting
     public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
         mDeviceStateManager = deviceStateManager;
+        registerCallbackIfNeededLocked();
     }
 
     /**
@@ -116,27 +118,22 @@
      * DeviceStateRequest.Callback)
      * @see DeviceStateRequest
      */
+    @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+            conditional = true)
     public void requestState(@NonNull DeviceStateRequest request,
             @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
-        if (callback == null && executor != null) {
-            throw new IllegalArgumentException("Callback must be supplied with executor.");
-        } else if (executor == null && callback != null) {
-            throw new IllegalArgumentException("Executor must be supplied with callback.");
-        }
-
+        DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+                executor);
         synchronized (mLock) {
-            registerCallbackIfNeededLocked();
-
             if (findRequestTokenLocked(request) != null) {
                 // This request has already been submitted.
                 return;
             }
-
             // Add the request wrapper to the mRequests array before requesting the state as the
             // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
             // same process as this instance.
             IBinder token = new Binder();
-            mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor));
+            mRequests.put(token, requestWrapper);
 
             try {
                 mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
@@ -153,10 +150,10 @@
      *
      * @see DeviceStateManager#cancelStateRequest
      */
+    @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+            conditional = true)
     public void cancelStateRequest() {
         synchronized (mLock) {
-            registerCallbackIfNeededLocked();
-
             try {
                 mDeviceStateManager.cancelStateRequest();
             } catch (RemoteException ex) {
@@ -166,6 +163,56 @@
     }
 
     /**
+     * Submits a {@link DeviceStateRequest request} to modify the base state of the device.
+     *
+     * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor,
+     * DeviceStateRequest.Callback)
+     * @see DeviceStateRequest
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+            @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
+        DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+                executor);
+        synchronized (mLock) {
+            if (findRequestTokenLocked(request) != null) {
+                // This request has already been submitted.
+                return;
+            }
+            // Add the request wrapper to the mRequests array before requesting the state as the
+            // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
+            // same process as this instance.
+            IBinder token = new Binder();
+            mRequests.put(token, requestWrapper);
+
+            try {
+                mDeviceStateManager.requestBaseStateOverride(token, request.getState(),
+                        request.getFlags());
+            } catch (RemoteException ex) {
+                mRequests.remove(token);
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+     * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     *
+     * @see DeviceStateManager#cancelBaseStateOverride
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void cancelBaseStateOverride() {
+        synchronized (mLock) {
+            try {
+                mDeviceStateManager.cancelBaseStateOverride();
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Registers a callback to receive notifications about changes in device state.
      *
      * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
@@ -179,9 +226,6 @@
                 // This callback is already registered.
                 return;
             }
-
-            registerCallbackIfNeededLocked();
-
             // Add the callback wrapper to the mCallbacks array after registering the callback as
             // the callback could be triggered immediately if the mDeviceStateManager IBinder is in
             // the same process as this instance.
@@ -357,6 +401,8 @@
 
         DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
                 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+            validateRequestWrapperParameters(callback, executor);
+
             mRequest = request;
             mCallback = callback;
             mExecutor = executor;
@@ -377,5 +423,14 @@
 
             mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest));
         }
+
+        private void validateRequestWrapperParameters(
+                @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+            if (callback == null && executor != null) {
+                throw new IllegalArgumentException("Callback must be supplied with executor.");
+            } else if (executor == null && callback != null) {
+                throw new IllegalArgumentException("Executor must be supplied with callback.");
+            }
+        }
     }
 }
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index e450e42..7175eae 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,6 +41,10 @@
      * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
      * call to this method.
      *
+     * Requesting a state does not cancel a base state override made through
+     * {@link #requestBaseStateOverride}, but will still attempt to put the device into the
+     * supplied {@code state}.
+     *
      * @param token the request token provided
      * @param state the state of device the request is asking for
      * @param flags any flags that correspond to the request
@@ -50,14 +54,53 @@
      * @throws IllegalStateException if the supplied {@code token} has already been registered.
      * @throws IllegalArgumentException if the supplied {@code state} is not supported.
      */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
     void requestState(IBinder token, int state, int flags);
 
     /**
      * Cancels the active request previously submitted with a call to
-     * {@link #requestState(IBinder, int, int)}.
+     * {@link #requestState(IBinder, int, int)}. Will have no effect on any base state override that
+     * was previously requested with {@link #requestBaseStateOverride}.
      *
      * @throws IllegalStateException if a callback has not yet been registered for the calling
      *         process.
      */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
     void cancelStateRequest();
+
+    /**
+     * Requests that the device's base state be overridden to the supplied {@code state}. A callback
+     * <b>MUST</b> have been previously registered with
+     * {@link #registerCallback(IDeviceStateManagerCallback)} before a call to this method.
+     *
+     * This method should only be used for testing, when you want to simulate the device physically
+     * changing states. If you are looking to change device state for a feature, where the system
+     * should still be aware that the physical state is different than the emulated state, use
+     * {@link #requestState}.
+     *
+     * @param token the request token provided
+     * @param state the state of device the request is asking for
+     * @param flags any flags that correspond to the request
+     *
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
+     * @throws IllegalStateException if the supplied {@code token} has already been registered.
+     * @throws IllegalArgumentException if the supplied {@code state} is not supported.
+     */
+    @JavaPassthrough(annotation=
+        "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+    void requestBaseStateOverride(IBinder token, int state, int flags);
+
+    /**
+     * Cancels the active base state request previously submitted with a call to
+     * {@link #overrideBaseState(IBinder, int, int)}.
+     *
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
+     */
+    @JavaPassthrough(annotation=
+        "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+    void cancelBaseStateOverride();
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8bc11cb..8311190 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -33,6 +33,7 @@
 import android.app.KeyguardManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.media.projection.MediaProjection;
@@ -40,6 +41,9 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -855,6 +859,16 @@
         return mGlobal.getUserDisabledHdrTypes();
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    @TestApi
+    public void overrideHdrTypes(int displayId, @NonNull int[] modes) {
+        mGlobal.overrideHdrTypes(displayId, modes);
+    }
 
     /**
      * Creates a virtual display.
@@ -1305,6 +1319,61 @@
     }
 
     /**
+     * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
+     * @param name The name for the virtual display
+     * @param width The initial width for the virtual display
+     * @param height The initial height for the virtual display
+     * @param displayIdToMirror The displayId that will be mirrored into the virtual display.
+     * @return VirtualDisplay that can be used to update properties.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+    @Nullable
+    @SystemApi
+    public static VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height,
+            int displayIdToMirror, @Nullable Surface surface) {
+        IDisplayManager sDm = IDisplayManager.Stub.asInterface(
+                ServiceManager.getService(Context.DISPLAY_SERVICE));
+        IPackageManager sPackageManager = IPackageManager.Stub.asInterface(
+                ServiceManager.getService("package"));
+
+        // Density doesn't matter since this virtual display is only used for mirroring.
+        VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+                height, 1 /* densityDpi */)
+                .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+                .setDisplayIdToMirror(displayIdToMirror);
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        VirtualDisplayConfig virtualDisplayConfig = builder.build();
+
+        String[] packages;
+        try {
+            packages = sPackageManager.getPackagesForUid(Process.myUid());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
+        // Just use the first one since it just needs to match the package when looking it up by
+        // calling UID in system server.
+        // The call may come from a rooted device, in that case the requesting uid will be root so
+        // it will not have any package name
+        String packageName = packages == null ? null : packages[0];
+        DisplayManagerGlobal.VirtualDisplayCallback
+                callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
+        int displayId;
+        try {
+            displayId = sDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null,
+                    packageName);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+        return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig,
+                null, callbackWrapper, displayId);
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e2f8592..9294dea 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,9 +20,11 @@
 import static android.hardware.display.DisplayManager.EventsMask;
 import static android.view.Display.HdrCapabilities.HdrType;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -577,6 +579,20 @@
         }
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     *
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    public void overrideHdrTypes(int displayId, int[] modes) {
+        try {
+            mDm.overrideHdrTypes(displayId, modes);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+
     public void requestColorMode(int displayId, int colorMode) {
         try {
             mDm.requestColorMode(displayId, colorMode);
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index fa3c450..b166e21 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -83,6 +83,9 @@
     // No permissions required.
     int[] getUserDisabledHdrTypes();
 
+    // Requires ACCESS_SURFACE_FLINGER permission.
+    void overrideHdrTypes(int displayId, in int[] modes);
+
     // Requires CONFIGURE_DISPLAY_COLOR_MODE
     void requestColorMode(int displayId, int colorMode);
 
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 9235ba1..3e509e4 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -36,10 +36,12 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ConcurrentUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -69,6 +71,32 @@
     private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
 
     /**
+     * A cache of the current device's physical address. When device's HDMI out port
+     * is not connected to any device, it is set to {@link #INVALID_PHYSICAL_ADDRESS}.
+     *
+     * <p>Otherwise it is updated by the {@link ClientHotplugEventListener} registered
+     * with {@link com.android.server.hdmi.HdmiControlService} by the
+     * {@link #addHotplugEventListener(HotplugEventListener)} and the address is from
+     * {@link com.android.server.hdmi.HdmiControlService#getPortInfo()}
+     */
+    @GuardedBy("mLock")
+    private int mLocalPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+    private void setLocalPhysicalAddress(int physicalAddress) {
+        synchronized (mLock) {
+            mLocalPhysicalAddress = physicalAddress;
+        }
+    }
+
+    private int getLocalPhysicalAddress() {
+        synchronized (mLock) {
+            return mLocalPhysicalAddress;
+        }
+    }
+
+    private final Object mLock = new Object();
+
+    /**
      * Broadcast Action: Display OSD message.
      * <p>Send when the service has a message to display on screen for events
      * that need user's attention such as ARC status change.
@@ -1094,6 +1122,37 @@
         mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
         mIsSwitchDevice = HdmiProperties.is_switch().orElse(false);
+        addHotplugEventListener(new ClientHotplugEventListener());
+    }
+
+    private final class ClientHotplugEventListener implements HotplugEventListener {
+
+        @Override
+        public void onReceived(HdmiHotplugEvent event) {
+            List<HdmiPortInfo> ports = new ArrayList<>();
+            try {
+                ports = mService.getPortInfo();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (ports.isEmpty()) {
+                Log.e(TAG, "Can't find port info, not updating connected status. "
+                        + "Hotplug event:" + event);
+                return;
+            }
+            // If the HDMI OUT port is plugged or unplugged, update the mLocalPhysicalAddress
+            for (HdmiPortInfo port : ports) {
+                if (port.getId() == event.getPort()) {
+                    if (port.getType() == HdmiPortInfo.PORT_OUTPUT) {
+                        setLocalPhysicalAddress(
+                                event.isConnected()
+                                        ? port.getAddress()
+                                        : INVALID_PHYSICAL_ADDRESS);
+                    }
+                    break;
+                }
+            }
+        }
     }
 
     private static boolean hasDeviceType(int[] types, int type) {
@@ -1464,11 +1523,7 @@
      * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
      */
     public int getPhysicalAddress() {
-        try {
-            return mService.getPhysicalAddress();
-        } catch (RemoteException e) {
-            return INVALID_PHYSICAL_ADDRESS;
-        }
+        return getLocalPhysicalAddress();
     }
 
     /**
@@ -1482,7 +1537,7 @@
      */
     public boolean isDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
         Objects.requireNonNull(targetDevice);
-        int physicalAddress = getPhysicalAddress();
+        int physicalAddress = getLocalPhysicalAddress();
         if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
             return false;
         }
@@ -1501,7 +1556,7 @@
     @Deprecated
     public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
         Objects.requireNonNull(targetDevice);
-        int physicalAddress = getPhysicalAddress();
+        int physicalAddress = getLocalPhysicalAddress();
         if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
             return false;
         }
diff --git a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
index dc5a966..8932435 100644
--- a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
+++ b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
@@ -16,14 +16,14 @@
 
 package android.hardware.input;
 
+import android.hardware.input.IInputDeviceBatteryState;
+
 /** @hide */
 oneway interface IInputDeviceBatteryListener {
 
     /**
      * Called when there is a change in battery state for a monitored device. This will be called
      * immediately after the listener is successfully registered for a new device via IInputManager.
-     * The parameters are values exposed through {@link android.hardware.BatteryState}.
      */
-    void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status, float capacity,
-            long eventTime);
+    void onBatteryStateChanged(in IInputDeviceBatteryState batteryState);
 }
diff --git a/core/java/android/hardware/input/IInputDeviceBatteryState.aidl b/core/java/android/hardware/input/IInputDeviceBatteryState.aidl
new file mode 100644
index 0000000..561286c
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDeviceBatteryState.aidl
@@ -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 android.hardware.input;
+
+/** @hide */
+@JavaDerive(equals=true)
+parcelable IInputDeviceBatteryState {
+    /** The deviceId of the input device that this battery state is associated with. */
+    int deviceId;
+
+    /**
+     * The timestamp of the last time the battery state was updated, in the
+     * {@link SystemClock.uptimeMillis()} time base.
+     */
+    long updateTime;
+
+    /** Whether the input device has a battery. */
+    boolean isPresent;
+
+    /** The battery status for this input device. */
+     @JavaPassthrough(annotation="@android.hardware.BatteryState.BatteryStatus")
+    int status;
+
+    /** The battery capacity for this input device, in a range between 0 and 1. */
+    float capacity;
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 36297b9..f213224b 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -21,6 +21,7 @@
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.TouchCalibration;
 import android.os.CombinedVibration;
@@ -110,9 +111,7 @@
     boolean registerVibratorStateListener(int deviceId, in IVibratorStateListener listener);
     boolean unregisterVibratorStateListener(int deviceId, in IVibratorStateListener listener);
 
-    // Input device battery query.
-    int getBatteryStatus(int deviceId);
-    int getBatteryCapacity(int deviceId);
+    IInputDeviceBatteryState getBatteryState(int deviceId);
 
     void setPointerIconType(int typeId);
     void setCustomPointerIcon(in PointerIcon icon);
diff --git a/core/java/android/hardware/input/InputDeviceBatteryState.java b/core/java/android/hardware/input/InputDeviceBatteryState.java
deleted file mode 100644
index d069eb0..0000000
--- a/core/java/android/hardware/input/InputDeviceBatteryState.java
+++ /dev/null
@@ -1,65 +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.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
-
-import android.hardware.BatteryState;
-
-/**
- * Battery implementation for input devices.
- *
- * @hide
- */
-public final class InputDeviceBatteryState extends BatteryState {
-    private static final float NULL_BATTERY_CAPACITY = Float.NaN;
-
-    private final InputManager mInputManager;
-    private final int mDeviceId;
-    private final boolean mHasBattery;
-
-    InputDeviceBatteryState(InputManager inputManager, int deviceId, boolean hasBattery) {
-        mInputManager = inputManager;
-        mDeviceId = deviceId;
-        mHasBattery = hasBattery;
-    }
-
-    @Override
-    public boolean isPresent() {
-        return mHasBattery;
-    }
-
-    @Override
-    public int getStatus() {
-        if (!mHasBattery) {
-            return BATTERY_STATUS_UNKNOWN;
-        }
-        return mInputManager.getBatteryStatus(mDeviceId);
-    }
-
-    @Override
-    public float getCapacity() {
-        if (mHasBattery) {
-            int capacity = mInputManager.getBatteryCapacity(mDeviceId);
-            if (capacity != INVALID_BATTERY_CAPACITY) {
-                return (float) capacity / 100.0f;
-            }
-        }
-        return NULL_BATTERY_CAPACITY;
-    }
-}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8960d2a..8d4aac4 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1308,32 +1308,6 @@
     }
 
     /**
-     * Get the battery status of the input device
-     * @param deviceId The input device ID
-     * @hide
-     */
-    public int getBatteryStatus(int deviceId) {
-        try {
-            return mIm.getBatteryStatus(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Get the remaining battery capacity of the input device
-     * @param deviceId The input device ID
-     * @hide
-     */
-    public int getBatteryCapacity(int deviceId) {
-        try {
-            return mIm.getBatteryCapacity(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
      * @param inputPort The port of the input device.
@@ -1622,8 +1596,17 @@
      * @return The battery, never null.
      * @hide
      */
-    public InputDeviceBatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
-        return new InputDeviceBatteryState(this, deviceId, hasBattery);
+    @NonNull
+    public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
+        if (!hasBattery) {
+            return new LocalBatteryState();
+        }
+        try {
+            final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
+            return new LocalBatteryState(state.isPresent, state.status, state.capacity);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -1767,8 +1750,8 @@
             listenersForDevice.mDelegates.add(delegate);
 
             // Notify the listener immediately if we already have the latest battery state.
-            if (listenersForDevice.mLatestBatteryState != null) {
-                delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
+            if (listenersForDevice.mInputDeviceBatteryState != null) {
+                delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
             }
         }
     }
@@ -1952,20 +1935,21 @@
         }
     }
 
+    // Implementation of the android.hardware.BatteryState interface used to report the battery
+    // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
     private static final class LocalBatteryState extends BatteryState {
-        final int mDeviceId;
-        final boolean mIsPresent;
-        final int mStatus;
-        final float mCapacity;
-        final long mEventTime;
+        private final boolean mIsPresent;
+        private final int mStatus;
+        private final float mCapacity;
 
-        LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
-                long eventTime) {
-            mDeviceId = deviceId;
+        LocalBatteryState() {
+            this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
+        }
+
+        LocalBatteryState(boolean isPresent, int status, float capacity) {
             mIsPresent = isPresent;
             mStatus = status;
             mCapacity = capacity;
-            mEventTime = eventTime;
         }
 
         @Override
@@ -1986,7 +1970,7 @@
 
     private static final class RegisteredBatteryListeners {
         final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
-        LocalBatteryState mLatestBatteryState;
+        IInputDeviceBatteryState mInputDeviceBatteryState;
     }
 
     private static final class InputDeviceBatteryListenerDelegate {
@@ -1998,27 +1982,24 @@
             mExecutor = executor;
         }
 
-        void notifyBatteryStateChanged(LocalBatteryState batteryState) {
+        void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
             mExecutor.execute(() ->
-                    mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
-                            batteryState));
+                    mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
+                            new LocalBatteryState(state.isPresent, state.status, state.capacity)));
         }
     }
 
     private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
         @Override
-        public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
-                float capacity, long eventTime) {
+        public void onBatteryStateChanged(IInputDeviceBatteryState state) {
             synchronized (mBatteryListenersLock) {
                 if (mBatteryListeners == null) return;
-                final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
+                final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
                 if (entry == null) return;
 
-                entry.mLatestBatteryState =
-                        new LocalBatteryState(
-                                deviceId, isBatteryPresent, status, capacity, eventTime);
+                entry.mInputDeviceBatteryState = state;
                 for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
-                    delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
+                    delegate.notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
                 }
             }
         }
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index c311379..1df9b75 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -60,15 +60,29 @@
     public static final int LIGHT_TYPE_PLAYER_ID = 10002;
 
     /**
+     * Type for lights that illuminate keyboard keys.
+     */
+    public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003;
+
+    /**
      * Capability for lights that could adjust its LED brightness. If the capability is not present
-     * the led can only be turned either on or off.
+     * the LED can only be turned either on or off.
      */
     public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1 << 0;
 
     /**
-     * Capability for lights that has red, green and blue LEDs to control the light's color.
+     * Capability for lights that have red, green and blue LEDs to control the light's color.
      */
-    public static final int LIGHT_CAPABILITY_RGB = 0 << 1;
+    public static final int LIGHT_CAPABILITY_COLOR_RGB = 1 << 1;
+
+    /**
+     * Capability for lights that have red, green and blue LEDs to control the light's color.
+     *
+     * @deprecated Wrong int based flag with value 0. Use capability flag {@code
+     * LIGHT_CAPABILITY_COLOR_RGB} instead.
+     */
+    @Deprecated
+    public static final int LIGHT_CAPABILITY_RGB = 0;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -77,6 +91,7 @@
             LIGHT_TYPE_MICROPHONE,
             LIGHT_TYPE_INPUT,
             LIGHT_TYPE_PLAYER_ID,
+            LIGHT_TYPE_KEYBOARD_BACKLIGHT,
         })
     public @interface LightType {}
 
@@ -85,6 +100,7 @@
     @IntDef(flag = true, prefix = {"LIGHT_CAPABILITY_"},
         value = {
             LIGHT_CAPABILITY_BRIGHTNESS,
+            LIGHT_CAPABILITY_COLOR_RGB,
             LIGHT_CAPABILITY_RGB,
         })
     public @interface LightCapability {}
@@ -233,7 +249,7 @@
      * @return True if the hardware can control the RGB led, otherwise false.
      */
     public boolean hasRgbControl() {
-        return (mCapabilities & LIGHT_CAPABILITY_RGB) == LIGHT_CAPABILITY_RGB;
+        return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB;
     }
 
 }
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a13eada..36ac1a0 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -279,7 +279,6 @@
         mPrimaryId = Objects.requireNonNull(primaryId);
         mSecondaryIds = secondaryIds;
         mVendorIds = vendorIds;
-        Arrays.sort(mSecondaryIds);
     }
 
     /**
@@ -525,7 +524,9 @@
         // vendorIds are ignored for equality
         // programType can be inferred from primaryId, thus not checked
         return mPrimaryId.equals(other.getPrimaryId())
-                && Arrays.equals(mSecondaryIds, other.mSecondaryIds);
+                && mSecondaryIds.length == other.mSecondaryIds.length
+                && Arrays.asList(mSecondaryIds).containsAll(
+                        Arrays.asList(other.mSecondaryIds));
     }
 
     private ProgramSelector(Parcel in) {
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index aa5480a..4a18333 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -16,12 +16,13 @@
 
 package android.hardware.radio;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -29,28 +30,36 @@
 /**
  * Implements the RadioTuner interface by forwarding calls to radio service.
  */
-class TunerAdapter extends RadioTuner {
+final class TunerAdapter extends RadioTuner {
     private static final String TAG = "BroadcastRadio.TunerAdapter";
 
-    @NonNull private final ITuner mTuner;
-    @NonNull private final TunerCallbackAdapter mCallback;
-    private boolean mIsClosed = false;
+    private final ITuner mTuner;
+    private final TunerCallbackAdapter mCallback;
+    private final Object mLock = new Object();
 
-    private @RadioManager.Band int mBand;
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
 
+    @GuardedBy("mLock")
+    @RadioManager.Band
+    private int mBand;
+
+    @GuardedBy("mLock")
     private ProgramList mLegacyListProxy;
+
+    @GuardedBy("mLock")
     private Map<String, String> mLegacyListFilter;
 
-    TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+    TunerAdapter(ITuner tuner, TunerCallbackAdapter callback,
             @RadioManager.Band int band) {
-        mTuner = Objects.requireNonNull(tuner);
-        mCallback = Objects.requireNonNull(callback);
+        mTuner = Objects.requireNonNull(tuner, "Tuner cannot be null");
+        mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
         mBand = band;
     }
 
     @Override
     public void close() {
-        synchronized (mTuner) {
+        synchronized (mLock) {
             if (mIsClosed) {
                 Log.v(TAG, "Tuner is already closed");
                 return;
@@ -60,8 +69,8 @@
                 mLegacyListProxy.close();
                 mLegacyListProxy = null;
             }
-            mCallback.close();
         }
+        mCallback.close();
         try {
             mTuner.close();
         } catch (RemoteException e) {
@@ -71,16 +80,20 @@
 
     @Override
     public int setConfiguration(RadioManager.BandConfig config) {
-        if (config == null) return RadioManager.STATUS_BAD_VALUE;
+        if (config == null) {
+            return RadioManager.STATUS_BAD_VALUE;
+        }
         try {
             mTuner.setConfiguration(config);
-            mBand = config.getType();
+            synchronized (mLock) {
+                mBand = config.getType();
+            }
             return RadioManager.STATUS_OK;
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Can't set configuration", e);
             return RadioManager.STATUS_BAD_VALUE;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
     }
@@ -94,7 +107,7 @@
             config[0] = mTuner.getConfiguration();
             return RadioManager.STATUS_OK;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
     }
@@ -107,7 +120,7 @@
             Log.e(TAG, "Can't set muted", e);
             return RadioManager.STATUS_ERROR;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -118,7 +131,7 @@
         try {
             return mTuner.isMuted();
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return true;
         }
     }
@@ -126,12 +139,13 @@
     @Override
     public int step(int direction, boolean skipSubChannel) {
         try {
-            mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+            mTuner.step(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't step", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -140,12 +154,13 @@
     @Override
     public int scan(int direction, boolean skipSubChannel) {
         try {
-            mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+            mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't scan", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -154,7 +169,11 @@
     @Override
     public int tune(int channel, int subChannel) {
         try {
-            mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
+            int band;
+            synchronized (mLock) {
+                band = mBand;
+            }
+            mTuner.tune(ProgramSelector.createAmFmSelector(band, channel, subChannel));
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't tune", e);
             return RadioManager.STATUS_INVALID_OPERATION;
@@ -162,18 +181,18 @@
             Log.e(TAG, "Can't tune", e);
             return RadioManager.STATUS_BAD_VALUE;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
     }
 
     @Override
-    public void tune(@NonNull ProgramSelector selector) {
+    public void tune(ProgramSelector selector) {
         try {
             mTuner.tune(selector);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -185,7 +204,7 @@
             Log.e(TAG, "Can't cancel", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -196,7 +215,7 @@
         try {
             mTuner.cancelAnnouncement();
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -217,11 +236,12 @@
     }
 
     @Override
-    public @Nullable Bitmap getMetadataImage(int id) {
+    @Nullable
+    public Bitmap getMetadataImage(int id) {
         try {
             return mTuner.getImage(id);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -230,66 +250,72 @@
         try {
             return mTuner.startBackgroundScan();
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull List<RadioManager.ProgramInfo>
+    public List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter) {
-        synchronized (mTuner) {
+        synchronized (mLock) {
             if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
                 Log.i(TAG, "Program list filter has changed, requesting new list");
                 mLegacyListProxy = new ProgramList();
                 mLegacyListFilter = vendorFilter;
-
                 mCallback.clearLastCompleteList();
-                mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
-                try {
-                    mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
-                } catch (RemoteException ex) {
-                    throw new RuntimeException("service died", ex);
-                }
+                mCallback.setProgramListObserver(mLegacyListProxy, () -> {
+                    Log.i(TAG, "Empty closeListener in programListObserver");
+                });
             }
-
-            List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
-            if (list == null) throw new IllegalStateException("Program list is not ready yet");
-            return list;
         }
+        try {
+            mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+        } catch (RemoteException ex) {
+            throw new RuntimeException("Service died", ex);
+        }
+
+        List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+        if (list == null) {
+            throw new IllegalStateException("Program list is not ready yet");
+        }
+        return list;
     }
 
     @Override
-    public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
-        synchronized (mTuner) {
+    @Nullable
+    public ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+        synchronized (mLock) {
             if (mLegacyListProxy != null) {
                 mLegacyListProxy.close();
                 mLegacyListProxy = null;
             }
             mLegacyListFilter = null;
-
-            ProgramList list = new ProgramList();
-            mCallback.setProgramListObserver(list, () -> {
-                try {
-                    mTuner.stopProgramListUpdates();
-                } catch (IllegalStateException ex) {
-                    // it's fine to not stop updates if tuner is already closed
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Couldn't stop program list updates", ex);
-                }
-            });
-
-            try {
-                mTuner.startProgramListUpdates(filter);
-            } catch (UnsupportedOperationException ex) {
-                Log.i(TAG, "Program list is not supported with this hardware");
-                return null;
-            } catch (RemoteException ex) {
-                mCallback.setProgramListObserver(null, () -> { });
-                throw new RuntimeException("service died", ex);
-            }
-
-            return list;
         }
+        ProgramList list = new ProgramList();
+        mCallback.setProgramListObserver(list, () -> {
+            try {
+                mTuner.stopProgramListUpdates();
+            } catch (IllegalStateException ex) {
+                // it's fine to not stop updates if tuner is already closed
+                Log.e(TAG, "Tuner may already be closed", ex);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Couldn't stop program list updates", ex);
+            }
+        });
+
+        try {
+            mTuner.startProgramListUpdates(filter);
+        } catch (UnsupportedOperationException ex) {
+            Log.i(TAG, "Program list is not supported with this hardware");
+            return null;
+        } catch (RemoteException ex) {
+            mCallback.setProgramListObserver(null, () -> {
+                Log.i(TAG, "Empty closeListener in programListObserver");
+            });
+            throw new RuntimeException("Service died", ex);
+        }
+
+        return list;
     }
 
     @Override
@@ -315,7 +341,7 @@
         try {
             return mTuner.isConfigFlagSupported(flag);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -324,7 +350,7 @@
         try {
             return mTuner.isConfigFlagSet(flag);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -333,25 +359,26 @@
         try {
             mTuner.setConfigFlag(flag, value);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
+    public Map<String, String> setParameters(Map<String, String> parameters) {
         try {
-            return mTuner.setParameters(Objects.requireNonNull(parameters));
+            return mTuner.setParameters(Objects.requireNonNull(parameters,
+                    "Parameters cannot be null"));
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) {
+    public Map<String, String> getParameters(List<String> keys) {
         try {
-            return mTuner.getParameters(Objects.requireNonNull(keys));
+            return mTuner.getParameters(Objects.requireNonNull(keys, "Keys cannot be null"));
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index e3f7001..b9782a8 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -16,12 +16,13 @@
 
 package android.hardware.radio;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -29,23 +30,31 @@
 /**
  * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
  */
-class TunerCallbackAdapter extends ITunerCallback.Stub {
+final class TunerCallbackAdapter extends ITunerCallback.Stub {
     private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
 
     private final Object mLock = new Object();
-    @NonNull private final RadioTuner.Callback mCallback;
-    @NonNull private final Handler mHandler;
+    private final RadioTuner.Callback mCallback;
+    private final Handler mHandler;
 
+    @GuardedBy("mLock")
     @Nullable ProgramList mProgramList;
 
     // cache for deprecated methods
+    @GuardedBy("mLock")
     boolean mIsAntennaConnected = true;
+
+    @GuardedBy("mLock")
     @Nullable List<RadioManager.ProgramInfo> mLastCompleteList;
-    private boolean mDelayedCompleteCallback = false;
+
+    @GuardedBy("mLock")
+    private boolean mDelayedCompleteCallback;
+
+    @GuardedBy("mLock")
     @Nullable RadioManager.ProgramInfo mCurrentProgramInfo;
 
-    TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
-        mCallback = callback;
+    TunerCallbackAdapter(RadioTuner.Callback callback, @Nullable Handler handler) {
+        mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
         if (handler == null) {
             mHandler = new Handler(Looper.getMainLooper());
         } else {
@@ -55,31 +64,39 @@
 
     void close() {
         synchronized (mLock) {
-            if (mProgramList != null) mProgramList.close();
+            if (mProgramList != null) {
+                mProgramList.close();
+            }
         }
     }
 
     void setProgramListObserver(@Nullable ProgramList programList,
-            @NonNull ProgramList.OnCloseListener closeListener) {
-        Objects.requireNonNull(closeListener);
+            ProgramList.OnCloseListener closeListener) {
+        Objects.requireNonNull(closeListener, "CloseListener cannot be null");
         synchronized (mLock) {
             if (mProgramList != null) {
                 Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
                 mProgramList.close();
             }
             mProgramList = programList;
-            if (programList == null) return;
+            if (programList == null) {
+                return;
+            }
             programList.setOnCloseListener(() -> {
                 synchronized (mLock) {
-                    if (mProgramList != programList) return;
+                    if (mProgramList != programList) {
+                        return;
+                    }
                     mProgramList = null;
                     mLastCompleteList = null;
-                    closeListener.onClose();
                 }
+                closeListener.onClose();
             });
             programList.addOnCompleteListener(() -> {
                 synchronized (mLock) {
-                    if (mProgramList != programList) return;
+                    if (mProgramList != programList) {
+                        return;
+                    }
                     mLastCompleteList = programList.toList();
                     if (mDelayedCompleteCallback) {
                         Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
@@ -109,7 +126,11 @@
     }
 
     boolean isAntennaConnected() {
-        return mIsAntennaConnected;
+        boolean isConnected;
+        synchronized (mLock) {
+            isConnected = mIsAntennaConnected;
+        }
+        return isConnected;
     }
 
     @Override
@@ -177,7 +198,9 @@
 
     @Override
     public void onAntennaState(boolean connected) {
-        mIsAntennaConnected = connected;
+        synchronized (mLock) {
+            mIsAntennaConnected = connected;
+        }
         mHandler.post(() -> mCallback.onAntennaState(connected));
     }
 
@@ -186,6 +209,7 @@
         mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
     }
 
+    @GuardedBy("mLock")
     private void sendBackgroundScanCompleteLocked() {
         mDelayedCompleteCallback = false;
         mHandler.post(() -> mCallback.onBackgroundScanComplete());
@@ -213,8 +237,10 @@
     public void onProgramListUpdated(ProgramList.Chunk chunk) {
         mHandler.post(() -> {
             synchronized (mLock) {
-                if (mProgramList == null) return;
-                mProgramList.apply(Objects.requireNonNull(chunk));
+                if (mProgramList == null) {
+                    return;
+                }
+                mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null"));
             }
         });
     }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 05daf63..a87b133 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -147,119 +147,146 @@
     @MainThread
     @Override
     public void executeMessage(Message msg) {
-        InputMethod inputMethod = mInputMethod.get();
-        // Need a valid reference to the inputMethod for everything except a dump.
-        if (inputMethod == null && msg.what != DO_DUMP) {
-            Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
-            return;
-        }
-
+        final InputMethod inputMethod = mInputMethod.get();
+        final InputMethodServiceInternal target = mTarget.get();
         switch (msg.what) {
             case DO_DUMP: {
-                InputMethodServiceInternal target = mTarget.get();
-                if (target == null) {
-                    return;
-                }
                 SomeArgs args = (SomeArgs)msg.obj;
-                try {
-                    target.dump((FileDescriptor) args.arg1,
-                            (PrintWriter) args.arg2, (String[]) args.arg3);
-                } catch (RuntimeException e) {
-                    ((PrintWriter)args.arg2).println("Exception: " + e);
-                }
-                synchronized (args.arg4) {
-                    ((CountDownLatch)args.arg4).countDown();
+                if (isValid(inputMethod, target, "DO_DUMP")) {
+                    final FileDescriptor fd = (FileDescriptor) args.arg1;
+                    final PrintWriter fout = (PrintWriter) args.arg2;
+                    final String[] dumpArgs = (String[]) args.arg3;
+                    final CountDownLatch latch = (CountDownLatch) args.arg4;
+                    try {
+                        target.dump(fd, fout, dumpArgs);
+                    } catch (RuntimeException e) {
+                        fout.println("Exception: " + e);
+                    } finally {
+                        latch.countDown();
+                    }
                 }
                 args.recycle();
                 return;
             }
             case DO_INITIALIZE_INTERNAL:
-                inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+                if (isValid(inputMethod, target, "DO_INITIALIZE_INTERNAL")) {
+                    inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+                }
                 return;
             case DO_SET_INPUT_CONTEXT: {
-                inputMethod.bindInput((InputBinding)msg.obj);
+                if (isValid(inputMethod, target, "DO_SET_INPUT_CONTEXT")) {
+                    inputMethod.bindInput((InputBinding) msg.obj);
+                }
                 return;
             }
             case DO_UNSET_INPUT_CONTEXT:
-                inputMethod.unbindInput();
+                if (isValid(inputMethod, target, "DO_UNSET_INPUT_CONTEXT")) {
+                    inputMethod.unbindInput();
+                }
                 return;
             case DO_START_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                final InputConnection inputConnection = (InputConnection) args.arg1;
-                final IInputMethod.StartInputParams params =
-                        (IInputMethod.StartInputParams) args.arg2;
-                inputMethod.dispatchStartInput(inputConnection, params);
+                if (isValid(inputMethod, target, "DO_START_INPUT")) {
+                    final InputConnection inputConnection = (InputConnection) args.arg1;
+                    final IInputMethod.StartInputParams params =
+                            (IInputMethod.StartInputParams) args.arg2;
+                    inputMethod.dispatchStartInput(inputConnection, params);
+                }
                 args.recycle();
                 return;
             }
             case DO_ON_NAV_BUTTON_FLAGS_CHANGED:
-                inputMethod.onNavButtonFlagsChanged(msg.arg1);
+                if (isValid(inputMethod, target, "DO_ON_NAV_BUTTON_FLAGS_CHANGED")) {
+                    inputMethod.onNavButtonFlagsChanged(msg.arg1);
+                }
                 return;
             case DO_CREATE_SESSION: {
                 SomeArgs args = (SomeArgs)msg.obj;
-                inputMethod.createSession(new InputMethodSessionCallbackWrapper(
-                        mContext, (InputChannel) args.arg1,
-                        (IInputMethodSessionCallback) args.arg2));
+                if (isValid(inputMethod, target, "DO_CREATE_SESSION")) {
+                    inputMethod.createSession(new InputMethodSessionCallbackWrapper(
+                            mContext, (InputChannel) args.arg1,
+                            (IInputMethodSessionCallback) args.arg2));
+                }
                 args.recycle();
                 return;
             }
             case DO_SET_SESSION_ENABLED:
-                inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
-                        msg.arg1 != 0);
+                if (isValid(inputMethod, target, "DO_SET_SESSION_ENABLED")) {
+                    inputMethod.setSessionEnabled((InputMethodSession) msg.obj, msg.arg1 != 0);
+                }
                 return;
             case DO_SHOW_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs)msg.obj;
-                inputMethod.showSoftInputWithToken(
-                        msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+                if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+                    inputMethod.showSoftInputWithToken(
+                            msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+                }
                 args.recycle();
                 return;
             }
             case DO_HIDE_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
-                        (IBinder) args.arg1);
+                if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+                    inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
+                            (IBinder) args.arg1);
+                }
                 args.recycle();
                 return;
             }
             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
-                inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
+                if (isValid(inputMethod, target, "DO_CHANGE_INPUTMETHOD_SUBTYPE")) {
+                    inputMethod.changeInputMethodSubtype((InputMethodSubtype) msg.obj);
+                }
                 return;
             case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                inputMethod.onCreateInlineSuggestionsRequest(
-                        (InlineSuggestionsRequestInfo) args.arg1,
-                        (IInlineSuggestionsRequestCallback) args.arg2);
+                if (isValid(inputMethod, target, "DO_CREATE_INLINE_SUGGESTIONS_REQUEST")) {
+                    inputMethod.onCreateInlineSuggestionsRequest(
+                            (InlineSuggestionsRequestInfo) args.arg1,
+                            (IInlineSuggestionsRequestCallback) args.arg2);
+                }
                 args.recycle();
                 return;
             }
             case DO_CAN_START_STYLUS_HANDWRITING: {
-                inputMethod.canStartStylusHandwriting(msg.arg1);
+                if (isValid(inputMethod, target, "DO_CAN_START_STYLUS_HANDWRITING")) {
+                    inputMethod.canStartStylusHandwriting(msg.arg1);
+                }
                 return;
             }
             case DO_UPDATE_TOOL_TYPE: {
-                inputMethod.updateEditorToolType(msg.arg1);
+                if (isValid(inputMethod, target, "DO_UPDATE_TOOL_TYPE")) {
+                    inputMethod.updateEditorToolType(msg.arg1);
+                }
                 return;
             }
             case DO_START_STYLUS_HANDWRITING: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
-                        (List<MotionEvent>) args.arg2);
+                if (isValid(inputMethod, target, "DO_START_STYLUS_HANDWRITING")) {
+                    inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
+                            (List<MotionEvent>) args.arg2);
+                }
                 args.recycle();
                 return;
             }
             case DO_INIT_INK_WINDOW: {
-                inputMethod.initInkWindow();
+                if (isValid(inputMethod, target, "DO_INIT_INK_WINDOW")) {
+                    inputMethod.initInkWindow();
+                }
                 return;
             }
             case DO_FINISH_STYLUS_HANDWRITING: {
-                inputMethod.finishStylusHandwriting();
+                if (isValid(inputMethod, target, "DO_FINISH_STYLUS_HANDWRITING")) {
+                    inputMethod.finishStylusHandwriting();
+                }
                 return;
             }
             case DO_REMOVE_STYLUS_HANDWRITING_WINDOW: {
-                inputMethod.removeStylusHandwritingWindow();
+                if (isValid(inputMethod, target, "DO_REMOVE_STYLUS_HANDWRITING_WINDOW")) {
+                    inputMethod.removeStylusHandwritingWindow();
+                }
                 return;
             }
-
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -445,4 +472,15 @@
     public void removeStylusHandwritingWindow() {
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW));
     }
+
+    private static boolean isValid(InputMethod inputMethod, InputMethodServiceInternal target,
+            String msg) {
+        if (inputMethod != null && target != null && !target.isServiceDestroyed()) {
+            return true;
+        } else {
+            Log.w(TAG, "Ignoring " + msg + ", InputMethod:" + inputMethod
+                    + ", InputMethodServiceInternal:" + target);
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 6f758de..891da24 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -775,4 +775,33 @@
             return false;
         }
     }
+
+    /**
+     * Invokes {@link IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
+     * CharSequence, TextAttribute)}.
+     *
+     * @param start the character index where the replacement should start.
+     * @param end the character index where the replacement should end.
+     * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
+     *     the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
+     *     of 1 will always advance you to the position after the full text being inserted. Note
+     *     that this means you can't position the cursor within the text.
+     * @param text the text to replace. This may include styles.
+     * @param textAttribute The extra information about the text. This value may be null.
+     */
+    @AnyThread
+    public boolean replaceText(
+            int start,
+            int end,
+            @NonNull CharSequence text,
+            int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        try {
+            mConnection.replaceText(
+                    createHeader(), start, end, text, newCursorPosition, textAttribute);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 6257d53..469b52bd91 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -163,6 +163,9 @@
         mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
             @Override
             public void onGlobalLayout() {
+                if (mInkView == null) {
+                    return;
+                }
                 if (mInkView.isVisibleToUser()) {
                     if (mInkViewVisibilityListener != null) {
                         mInkViewVisibilityListener.onInkViewVisible();
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7436601..85a8551 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -65,6 +65,7 @@
 import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -546,6 +547,20 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
     public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // This is a bug id.
 
+    /**
+     * Disallow IMEs to override {@link InputMethodService#onCreateInputMethodSessionInterface()}
+     * method.
+     *
+     * <p>If IMEs targeting on Android U and beyond override the
+     * {@link InputMethodService#onCreateInputMethodSessionInterface()}, an {@link LinkageError}
+     * would be thrown.</p>
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+
     LayoutInflater mInflater;
     TypedArray mThemeAttrs;
     @UnsupportedAppUsage
@@ -700,11 +715,6 @@
         @MainThread
         @Override
         public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
-            if (mDestroyed) {
-                Log.i(TAG, "The InputMethodService has already onDestroyed()."
-                    + "Ignore the initialization.");
-                return;
-            }
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
             mConfigTracker.onInitialize(params.configChanges);
             mPrivOps.set(params.privilegedOperations);
@@ -967,7 +977,7 @@
                 Log.d(TAG, "Input should have started before starting Stylus handwriting.");
                 return;
             }
-            maybeCreateInkWindow();
+            maybeCreateAndInitInkWindow();
             if (!mOnPreparedStylusHwCalled) {
                 // prepare hasn't been called by Stylus HOVER.
                 onPrepareStylusHandwriting();
@@ -1027,21 +1037,21 @@
          */
         @Override
         public void initInkWindow() {
-            maybeCreateInkWindow();
-            mInkWindow.initOnly();
+            maybeCreateAndInitInkWindow();
             onPrepareStylusHandwriting();
             mOnPreparedStylusHwCalled = true;
         }
 
         /**
-         * Create and attach token to Ink window if it wasn't already created.
+         * Create, attach token and layout Ink window if it wasn't already created.
          */
-        private void maybeCreateInkWindow() {
+        private void maybeCreateAndInitInkWindow() {
             if (mInkWindow == null) {
                 mInkWindow = new InkWindow(mWindow.getContext());
                 mInkWindow.setToken(mToken);
             }
             // TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
+            mInkWindow.initOnly();
         }
 
         /**
@@ -1532,6 +1542,11 @@
     }
 
     @Override public void onCreate() {
+        if (methodIsOverridden("onCreateInputMethodSessionInterface")
+                && CompatChanges.isChangeEnabled(DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE)) {
+            throw new LinkageError("InputMethodService#onCreateInputMethodSessionInterface()"
+                    + " can no longer be overridden!");
+        }
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.onCreate");
         mTheme = Resources.selectSystemTheme(mTheme,
                 getApplicationInfo().targetSdkVersion,
@@ -1769,6 +1784,9 @@
      * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
      * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
      * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
+     *
+     * <p>IMEs targeting on Android U and above cannot override this method, or an
+     * {@link LinkageError} would be thrown.</p>
      */
     @Deprecated
     @Override
@@ -2469,21 +2487,26 @@
      * @param motionEvent {@link MotionEvent} from stylus.
      */
     public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
-        if (mInkWindow.isInkViewVisible()) {
+        if (mInkWindow != null && mInkWindow.isInkViewVisible()) {
             mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
         } else {
             if (mPendingEvents == null) {
                 mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
             }
             mPendingEvents.append(motionEvent);
-            mInkWindow.setInkViewVisibilityListener(() -> {
-                if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
-                    for (MotionEvent event : mPendingEvents.toArray()) {
-                        mInkWindow.getDecorView().dispatchTouchEvent(event);
+            if (mInkWindow != null) {
+                mInkWindow.setInkViewVisibilityListener(() -> {
+                    if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
+                        for (MotionEvent event : mPendingEvents.toArray()) {
+                            if (mInkWindow == null) {
+                                break;
+                            }
+                            mInkWindow.getDecorView().dispatchTouchEvent(event);
+                        }
+                        mPendingEvents.clear();
                     }
-                    mPendingEvents.clear();
-                }
-            });
+                });
+            }
         }
     }
 
@@ -3938,6 +3961,14 @@
             public void triggerServiceDump(String where, @Nullable byte[] icProto) {
                 ImeTracing.getInstance().triggerServiceDump(where, mDumper, icProto);
             }
+
+            /**
+             * {@inheritDoc}
+             */
+            @Override
+            public boolean isServiceDestroyed() {
+                return mDestroyed;
+            }
         };
     }
 
@@ -4064,4 +4095,13 @@
         final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
         onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
     }
+
+    private boolean methodIsOverridden(String methodName, Class<?>... parameterTypes) {
+        try {
+            return getClass().getMethod(methodName, parameterTypes).getDeclaringClass()
+                    != InputMethodService.class;
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("Method must exist.", e);
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
index f44f49d..c6612f6 100644
--- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java
+++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
@@ -85,4 +85,11 @@
      */
     default void triggerServiceDump(@NonNull String where, @Nullable byte[] icProto) {
     }
+
+    /**
+     * @return {@code true} if {@link InputMethodService} is destroyed.
+     */
+    default boolean isServiceDestroyed() {
+        return false;
+    };
 }
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 694293c..09e86c4 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -50,7 +50,7 @@
  * Takes care of remote method invocations of {@link InputConnection} in the IME side.
  *
  * <p>This class works as a proxy to forward API calls on {@link InputConnection} to
- * {@link com.android.internal.inputmethod.RemoteInputConnectionImpl} running on the IME client
+ * {@link android.view.inputmethod.RemoteInputConnectionImpl} running on the IME client
  * (editor app) process then waits replies as needed.</p>
  *
  * <p>See also {@link IRemoteInputConnection} for the actual {@link android.os.Binder} IPC protocols
@@ -498,6 +498,17 @@
         return mInvoker.setImeConsumesInput(imeConsumesInput);
     }
 
+    /** See {@link InputConnection#replaceText(int, int, CharSequence, int, TextAttribute)}. */
+    @AnyThread
+    public boolean replaceText(
+            int start,
+            int end,
+            @NonNull CharSequence text,
+            int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        return mInvoker.replaceText(start, end, text, newCursorPosition, textAttribute);
+    }
+
     @AnyThread
     @Override
     public String toString() {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 2b34d86..0b56d19 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -209,7 +209,8 @@
      */
     public boolean isDefaultServiceForCategory(ComponentName service, String category) {
         try {
-            return sService.isDefaultServiceForCategory(mContext.getUserId(), service, category);
+            return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                    service, category);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -218,8 +219,8 @@
                 return false;
             }
             try {
-                return sService.isDefaultServiceForCategory(mContext.getUserId(), service,
-                        category);
+                return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                        service, category);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover CardEmulationService.");
                 return false;
@@ -240,7 +241,8 @@
      */
     public boolean isDefaultServiceForAid(ComponentName service, String aid) {
         try {
-            return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
+            return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+                    service, aid);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -249,7 +251,8 @@
                 return false;
             }
             try {
-                return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
+                return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+                        service, aid);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -348,7 +351,8 @@
             List<String> aids) {
         AidGroup aidGroup = new AidGroup(aids, category);
         try {
-            return sService.registerAidGroupForService(mContext.getUserId(), service, aidGroup);
+            return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+                    service, aidGroup);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -357,8 +361,8 @@
                 return false;
             }
             try {
-                return sService.registerAidGroupForService(mContext.getUserId(), service,
-                        aidGroup);
+                return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, aidGroup);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -391,7 +395,7 @@
         }
 
         try {
-            return sService.unsetOffHostForService(mContext.getUserId(), service);
+            return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -400,7 +404,7 @@
                 return false;
             }
             try {
-                return sService.unsetOffHostForService(mContext.getUserId(), service);
+                return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -468,7 +472,7 @@
         }
 
         try {
-            return sService.setOffHostForService(mContext.getUserId(), service,
+            return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
                 offHostSecureElement);
         } catch (RemoteException e) {
             // Try one more time
@@ -478,7 +482,7 @@
                 return false;
             }
             try {
-                return sService.setOffHostForService(mContext.getUserId(), service,
+                return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
                         offHostSecureElement);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
@@ -503,8 +507,8 @@
      */
     public List<String> getAidsForService(ComponentName service, String category) {
         try {
-            AidGroup group =  sService.getAidGroupForService(mContext.getUserId(), service,
-                    category);
+            AidGroup group =  sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+                    service, category);
             return (group != null ? group.getAids() : null);
         } catch (RemoteException e) {
             recoverService();
@@ -513,8 +517,8 @@
                 return null;
             }
             try {
-                AidGroup group = sService.getAidGroupForService(mContext.getUserId(), service,
-                        category);
+                AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, category);
                 return (group != null ? group.getAids() : null);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover CardEmulationService.");
@@ -540,7 +544,8 @@
      */
     public boolean removeAidsForService(ComponentName service, String category) {
         try {
-            return sService.removeAidGroupForService(mContext.getUserId(), service, category);
+            return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
+                    category);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -549,7 +554,8 @@
                 return false;
             }
             try {
-                return sService.removeAidGroupForService(mContext.getUserId(), service, category);
+                return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, category);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -684,7 +690,8 @@
     @Nullable
     public List<String> getAidsForPreferredPaymentService() {
         try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
             return (serviceInfo != null ? serviceInfo.getAids() : null);
         } catch (RemoteException e) {
             recoverService();
@@ -694,7 +701,7 @@
             }
             try {
                 ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUserId());
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
                 return (serviceInfo != null ? serviceInfo.getAids() : null);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover CardEmulationService.");
@@ -723,7 +730,8 @@
     @Nullable
     public String getRouteDestinationForPreferredPaymentService() {
         try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
             if (serviceInfo != null) {
                 if (!serviceInfo.isOnHost()) {
                     return serviceInfo.getOffHostSecureElement() == null ?
@@ -740,7 +748,7 @@
             }
             try {
                 ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUserId());
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
                 if (serviceInfo != null) {
                     if (!serviceInfo.isOnHost()) {
                         return serviceInfo.getOffHostSecureElement() == null ?
@@ -766,7 +774,8 @@
     @Nullable
     public CharSequence getDescriptionForPreferredPaymentService() {
         try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
             return (serviceInfo != null ? serviceInfo.getDescription() : null);
         } catch (RemoteException e) {
             recoverService();
@@ -776,7 +785,7 @@
             }
             try {
                 ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUserId());
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
                 return (serviceInfo != null ? serviceInfo.getDescription() : null);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover CardEmulationService.");
@@ -790,7 +799,8 @@
      */
     public boolean setDefaultServiceForCategory(ComponentName service, String category) {
         try {
-            return sService.setDefaultServiceForCategory(mContext.getUserId(), service, category);
+            return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                    service, category);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -799,8 +809,8 @@
                 return false;
             }
             try {
-                return sService.setDefaultServiceForCategory(mContext.getUserId(), service,
-                        category);
+                return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                        service, category);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -813,7 +823,7 @@
      */
     public boolean setDefaultForNextTap(ComponentName service) {
         try {
-            return sService.setDefaultForNextTap(mContext.getUserId(), service);
+            return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -822,7 +832,7 @@
                 return false;
             }
             try {
-                return sService.setDefaultForNextTap(mContext.getUserId(), service);
+                return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -857,7 +867,7 @@
      */
     public List<ApduServiceInfo> getServices(String category) {
         try {
-            return sService.getServices(mContext.getUserId(), category);
+            return sService.getServices(mContext.getUser().getIdentifier(), category);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -866,7 +876,7 @@
                 return null;
             }
             try {
-                return sService.getServices(mContext.getUserId(), category);
+                return sService.getServices(mContext.getUser().getIdentifier(), category);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return null;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 557e41a..3c92455 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -117,7 +117,7 @@
             throw new NullPointerException("service is null");
         }
         try {
-            return sService.getSystemCodeForService(mContext.getUserId(), service);
+            return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), service);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -126,7 +126,8 @@
                 return null;
             }
             try {
-                return sService.getSystemCodeForService(mContext.getUserId(), service);
+                return sService.getSystemCodeForService(mContext.getUser().getIdentifier(),
+                        service);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 ee.rethrowAsRuntimeException();
@@ -163,7 +164,7 @@
             throw new NullPointerException("service or systemCode is null");
         }
         try {
-            return sService.registerSystemCodeForService(mContext.getUserId(),
+            return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
                     service, systemCode);
         } catch (RemoteException e) {
             // Try one more time
@@ -173,7 +174,7 @@
                 return false;
             }
             try {
-                return sService.registerSystemCodeForService(mContext.getUserId(),
+                return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
                         service, systemCode);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
@@ -194,7 +195,7 @@
             throw new NullPointerException("service is null");
         }
         try {
-            return sService.removeSystemCodeForService(mContext.getUserId(), service);
+            return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), service);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -203,7 +204,8 @@
                 return false;
             }
             try {
-                return sService.removeSystemCodeForService(mContext.getUserId(), service);
+                return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(),
+                        service);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 ee.rethrowAsRuntimeException();
@@ -229,7 +231,7 @@
             throw new NullPointerException("service is null");
         }
         try {
-            return sService.getNfcid2ForService(mContext.getUserId(), service);
+            return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -238,7 +240,7 @@
                 return null;
             }
             try {
-                return sService.getNfcid2ForService(mContext.getUserId(), service);
+                return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 ee.rethrowAsRuntimeException();
@@ -272,7 +274,7 @@
             throw new NullPointerException("service or nfcid2 is null");
         }
         try {
-            return sService.setNfcid2ForService(mContext.getUserId(),
+            return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
                     service, nfcid2);
         } catch (RemoteException e) {
             // Try one more time
@@ -282,7 +284,7 @@
                 return false;
             }
             try {
-                return sService.setNfcid2ForService(mContext.getUserId(),
+                return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
                         service, nfcid2);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
@@ -380,7 +382,7 @@
      */
     public List<NfcFServiceInfo> getNfcFServices() {
         try {
-            return sService.getNfcFServices(mContext.getUserId());
+            return sService.getNfcFServices(mContext.getUser().getIdentifier());
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -389,7 +391,7 @@
                 return null;
             }
             try {
-                return sService.getNfcFServices(mContext.getUserId());
+                return sService.getNfcFServices(mContext.getUser().getIdentifier());
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return null;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d71b023..adeb722 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -296,8 +296,10 @@
      * New in version 35:
      *   - Fixed bug that was not reporting high cellular tx power correctly
      *   - Added out of service and emergency service modes to data connection types
+     * New in version 36:
+     *   - Added PowerStats and CPU time-in-state data
      */
-    static final int CHECKIN_VERSION = 35;
+    static final int CHECKIN_VERSION = 36;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -1810,6 +1812,36 @@
     }
 
     /**
+     * CPU usage for a given UID.
+     */
+    public static final class CpuUsageDetails {
+        /**
+         * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+         */
+        public String[] cpuBracketDescriptions;
+        public int uid;
+        /**
+         *  The delta, in milliseconds, per CPU power bracket, from the previous record for the
+         *  same UID.
+         */
+        public long[] cpuUsageMs;
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            UserHandle.formatUid(sb, uid);
+            sb.append(": ");
+            for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
+                if (bracket != 0) {
+                    sb.append(", ");
+                }
+                sb.append(cpuUsageMs[bracket]);
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
      * Battery history record.
      */
     public static final class HistoryItem {
@@ -1952,6 +1984,9 @@
         // Non-null when there is measured energy information
         public MeasuredEnergyDetails measuredEnergyDetails;
 
+        // Non-null when there is CPU usage information
+        public CpuUsageDetails cpuUsageDetails;
+
         public static final int EVENT_FLAG_START = 0x8000;
         public static final int EVENT_FLAG_FINISH = 0x4000;
 
@@ -2161,6 +2196,7 @@
             eventTag = null;
             tagsFirstOccurrence = false;
             measuredEnergyDetails = null;
+            cpuUsageDetails = null;
         }
 
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2211,6 +2247,7 @@
             tagsFirstOccurrence = o.tagsFirstOccurrence;
             currentTime = o.currentTime;
             measuredEnergyDetails = o.measuredEnergyDetails;
+            cpuUsageDetails = o.cpuUsageDetails;
         }
 
         public boolean sameNonEvent(HistoryItem o) {
@@ -6808,6 +6845,25 @@
         private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
                 boolean verbose) {
             StringBuilder item = new StringBuilder();
+
+            if (rec.cpuUsageDetails != null
+                    && rec.cpuUsageDetails.cpuBracketDescriptions != null
+                    && checkin) {
+                String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+                for (int bracket = 0; bracket < descriptions.length; bracket++) {
+                    item.append(BATTERY_STATS_CHECKIN_VERSION);
+                    item.append(',');
+                    item.append(HISTORY_DATA);
+                    item.append(",0,XB,");
+                    item.append(descriptions.length);
+                    item.append(',');
+                    item.append(bracket);
+                    item.append(',');
+                    item.append(descriptions[bracket]);
+                    item.append("\n");
+                }
+            }
+
             if (!checkin) {
                 item.append("  ");
                 TimeUtils.formatDuration(
@@ -7000,14 +7056,6 @@
                         item.append("\"");
                     }
                 }
-                if ((rec.states2 & HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
-                    if (!checkin) {
-                        item.append(" ext=");
-                        if (rec.measuredEnergyDetails != null) {
-                            item.append("E");
-                        }
-                    }
-                }
                 if (rec.eventCode != HistoryItem.EVENT_NONE) {
                     item.append(checkin ? "," : " ");
                     if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
@@ -7036,6 +7084,58 @@
                         item.append("\"");
                     }
                 }
+                boolean firstExtension = true;
+                if (rec.measuredEnergyDetails != null) {
+                    firstExtension = false;
+                    if (!checkin) {
+                        item.append(" ext=energy:");
+                        item.append(rec.measuredEnergyDetails);
+                    } else {
+                        item.append(",XE");
+                        for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
+                            if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+                                item.append(',');
+                                item.append(rec.measuredEnergyDetails.consumers[i].name);
+                                item.append('=');
+                                item.append(rec.measuredEnergyDetails.chargeUC[i]);
+                            }
+                        }
+                    }
+                }
+                if (rec.cpuUsageDetails != null) {
+                    if (!checkin) {
+                        if (!firstExtension) {
+                            item.append("\n                ");
+                        }
+                        String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+                        if (descriptions != null) {
+                            for (int bracket = 0; bracket < descriptions.length; bracket++) {
+                                item.append(" ext=cpu-bracket:");
+                                item.append(bracket);
+                                item.append(":");
+                                item.append(descriptions[bracket]);
+                                item.append("\n                ");
+                            }
+                        }
+                        item.append(" ext=cpu:");
+                        item.append(rec.cpuUsageDetails);
+                    } else {
+                        if (!firstExtension) {
+                            item.append('\n');
+                            item.append(BATTERY_STATS_CHECKIN_VERSION);
+                            item.append(',');
+                            item.append(HISTORY_DATA);
+                            item.append(",0");
+                        }
+                        item.append(",XC,");
+                        item.append(rec.cpuUsageDetails.uid);
+                        for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
+                            item.append(',');
+                            item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
+                        }
+                    }
+                    firstExtension = false;
+                }
                 item.append("\n");
                 if (rec.stepDetails != null) {
                     if (!checkin) {
@@ -7132,25 +7232,6 @@
                         item.append("\n");
                     }
                 }
-                if (rec.measuredEnergyDetails != null) {
-                    if (!checkin) {
-                        item.append("                 Energy: ");
-                        item.append(rec.measuredEnergyDetails);
-                        item.append("\n");
-                    } else {
-                        item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
-                        item.append(HISTORY_DATA); item.append(",0,XE");
-                        for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
-                            if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
-                                item.append(',');
-                                item.append(rec.measuredEnergyDetails.consumers[i].name);
-                                item.append('=');
-                                item.append(rec.measuredEnergyDetails.chargeUC[i]);
-                            }
-                        }
-                        item.append("\n");
-                    }
-                }
                 oldState = rec.states;
                 oldState2 = rec.states2;
                 // Clear High Tx Power Flag for volta positioning
@@ -7158,7 +7239,6 @@
                     rec.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
                 }
             }
-
             return item.toString();
         }
 
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d2e5f9e..4df0139 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -45,6 +45,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.reflect.Modifier;
+import java.util.concurrent.atomic.AtomicReferenceArray;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -291,7 +292,7 @@
     private IInterface mOwner;
     @Nullable
     private String mDescriptor;
-    private volatile String[] mTransactionTraceNames = null;
+    private volatile AtomicReferenceArray<String> mTransactionTraceNames = null;
     private volatile String mSimpleDescriptor = null;
     private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024;
 
@@ -895,28 +896,32 @@
     @VisibleForTesting
     public final @NonNull String getTransactionTraceName(int transactionCode) {
         if (mTransactionTraceNames == null) {
-            final String descriptor = getSimpleDescriptor();
             final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
-            final String[] transactionNames = new String[highestId + 1];
-            final StringBuffer buf = new StringBuffer();
-            for (int i = 0; i <= highestId; i++) {
-                String transactionName = getTransactionName(i + FIRST_CALL_TRANSACTION);
-                if (transactionName != null) {
-                    buf.append(descriptor).append(':').append(transactionName);
-                } else {
-                    buf.append(descriptor).append('#').append(i + FIRST_CALL_TRANSACTION);
-                }
-                transactionNames[i] = buf.toString();
-                buf.setLength(0);
-            }
-            mSimpleDescriptor = descriptor;
-            mTransactionTraceNames = transactionNames;
+            mSimpleDescriptor = getSimpleDescriptor();
+            mTransactionTraceNames = new AtomicReferenceArray(highestId + 1);
         }
+
         final int index = transactionCode - FIRST_CALL_TRANSACTION;
-        if (index < 0 || index >= mTransactionTraceNames.length) {
+        if (index < 0 || index >= mTransactionTraceNames.length()) {
             return mSimpleDescriptor + "#" + transactionCode;
         }
-        return mTransactionTraceNames[index];
+
+        String transactionTraceName = mTransactionTraceNames.getAcquire(index);
+        if (transactionTraceName == null) {
+            final String transactionName = getTransactionName(transactionCode);
+            final StringBuffer buf = new StringBuffer();
+
+            if (transactionName != null) {
+                buf.append(mSimpleDescriptor).append(":").append(transactionName);
+            } else {
+                buf.append(mSimpleDescriptor).append("#").append(transactionCode);
+            }
+
+            transactionTraceName = buf.toString();
+            mTransactionTraceNames.setRelease(index, transactionTraceName);
+        }
+
+        return transactionTraceName;
     }
 
     private @NonNull String getSimpleDescriptor() {
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index edfcb3d..d5c3de1 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -54,6 +54,7 @@
 import android.system.Os;
 import android.system.StructStat;
 import android.text.TextUtils;
+import android.util.DataUnit;
 import android.util.Log;
 import android.util.Slog;
 import android.webkit.MimeTypeMap;
@@ -83,6 +84,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -1309,6 +1311,85 @@
         return val * pow;
     }
 
+    private static long toBytes(long value, String unit) {
+        unit = unit.toUpperCase();
+
+        if (List.of("B").contains(unit)) {
+            return value;
+        }
+
+        if (List.of("K", "KB").contains(unit)) {
+            return DataUnit.KILOBYTES.toBytes(value);
+        }
+
+        if (List.of("M", "MB").contains(unit)) {
+            return DataUnit.MEGABYTES.toBytes(value);
+        }
+
+        if (List.of("G", "GB").contains(unit)) {
+            return DataUnit.GIGABYTES.toBytes(value);
+        }
+
+        if (List.of("KI", "KIB").contains(unit)) {
+            return DataUnit.KIBIBYTES.toBytes(value);
+        }
+
+        if (List.of("MI", "MIB").contains(unit)) {
+            return DataUnit.MEBIBYTES.toBytes(value);
+        }
+
+        if (List.of("GI", "GIB").contains(unit)) {
+            return DataUnit.GIBIBYTES.toBytes(value);
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    /**
+     * @param fmtSize The string that contains the size to be parsed. The
+     *   expected format is:
+     *
+     *   <p>"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$"
+     *
+     *   <p>For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive.
+     *
+     * @return the size in bytes. If {@code fmtSize} has invalid format, it
+     *   returns {@link Long#MIN_VALUE}.
+     * @hide
+     */
+    public static long parseSize(@Nullable String fmtSize) {
+        if (fmtSize == null || fmtSize.isBlank()) {
+            return Long.MIN_VALUE;
+        }
+
+        int sign = 1;
+        fmtSize = fmtSize.trim();
+        char first = fmtSize.charAt(0);
+        if (first == '-' ||  first == '+') {
+            if (first == '-') {
+                sign = -1;
+            }
+
+            fmtSize = fmtSize.replace(first + "", "");
+        }
+
+        int index = 0;
+        // Find the last index of the value in fmtSize.
+        while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) {
+            index++;
+        }
+
+        // Check if number and units are present.
+        if (index == 0 || index == fmtSize.length()) {
+            return Long.MIN_VALUE;
+        }
+
+        long value = sign * Long.valueOf(fmtSize.substring(0, index));
+        String unit = fmtSize.substring(index).trim();
+
+        return toBytes(value, unit);
+    }
+
     /**
      * Closes the given object quietly, ignoring any checked exceptions. Does
      * nothing if the given object is {@code null}.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9189c6c..d451765 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -559,7 +559,9 @@
      */
     public final void recycle() {
         if (mRecycled) {
-            Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?)", mStack);
+            Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: "
+                    + Log.getStackTraceString(new Throwable())
+                    + " Original recycle call (if DEBUG_RECYCLE): ", mStack);
         }
         mRecycled = true;
 
@@ -3666,9 +3668,6 @@
      * previously been written via {@link #writeTypedList} with the same object
      * type.
      *
-     * @return A newly created ArrayList containing objects with the same data
-     *         as those that were previously written.
-     *
      * @see #writeTypedList
      */
     public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) {
@@ -4419,6 +4418,9 @@
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index f4edcb1..acfd15c 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
+import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -50,6 +51,8 @@
  */
 public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
         XmlUtils.WriteMapCallback {
+    private static final String TAG = "PersistableBundle";
+
     private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
 
     /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */
@@ -118,7 +121,11 @@
      * @hide
      */
     public PersistableBundle(Bundle b) {
-        this(b.getItemwiseMap());
+        this(b, true);
+    }
+
+    private PersistableBundle(Bundle b, boolean throwException) {
+        this(b.getItemwiseMap(), throwException);
     }
 
     /**
@@ -127,7 +134,7 @@
      * @param map a Map containing only those items that can be persisted.
      * @throws IllegalArgumentException if any element of #map cannot be persisted.
      */
-    private PersistableBundle(ArrayMap<String, Object> map) {
+    private PersistableBundle(ArrayMap<String, Object> map, boolean throwException) {
         super();
         mFlags = FLAG_DEFUSABLE;
 
@@ -136,16 +143,23 @@
 
         // Now verify each item throwing an exception if there is a violation.
         final int N = mMap.size();
-        for (int i=0; i<N; i++) {
+        for (int i = N - 1; i >= 0; --i) {
             Object value = mMap.valueAt(i);
             if (value instanceof ArrayMap) {
                 // Fix up any Maps by replacing them with PersistableBundles.
-                mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
+                mMap.setValueAt(i,
+                        new PersistableBundle((ArrayMap<String, Object>) value, throwException));
             } else if (value instanceof Bundle) {
-                mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
+                mMap.setValueAt(i, new PersistableBundle((Bundle) value, throwException));
             } else if (!isValidType(value)) {
-                throw new IllegalArgumentException("Bad value in PersistableBundle key="
-                        + mMap.keyAt(i) + " value=" + value);
+                final String errorMsg = "Bad value in PersistableBundle key="
+                        + mMap.keyAt(i) + " value=" + value;
+                if (throwException) {
+                    throw new IllegalArgumentException(errorMsg);
+                } else {
+                    Slog.wtfStack(TAG, errorMsg);
+                    mMap.removeAt(i);
+                }
             }
         }
     }
@@ -268,6 +282,15 @@
     /** @hide */
     public void saveToXml(TypedXmlSerializer out) throws IOException, XmlPullParserException {
         unparcel();
+        // Explicitly drop invalid types an attacker may have added before persisting.
+        for (int i = mMap.size() - 1; i >= 0; --i) {
+            final Object value = mMap.valueAt(i);
+            if (!isValidType(value)) {
+                Slog.e(TAG, "Dropping bad data before persisting: "
+                        + mMap.keyAt(i) + "=" + value);
+                mMap.removeAt(i);
+            }
+        }
         XmlUtils.writeMapXml(mMap, out, this);
     }
 
@@ -322,9 +345,12 @@
         while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
                 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
             if (event == XmlPullParser.START_TAG) {
+                // Don't throw an exception when restoring from XML since an attacker could try to
+                // input invalid data in the persisted file.
                 return new PersistableBundle((ArrayMap<String, Object>)
                         XmlUtils.readThisArrayMapXml(in, startTag, tagName,
-                        new MyReadMapCallback()));
+                        new MyReadMapCallback()),
+                        /* throwException */ false);
             }
         }
         return new PersistableBundle();  // An empty mutable PersistableBundle
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c0e2864..9bc7ffd 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2913,6 +2913,7 @@
         private int mFlags;
         @UnsupportedAppUsage
         private String mTag;
+        private int mTagHash;
         private final String mPackageName;
         private final IBinder mToken;
         private int mInternalCount;
@@ -2921,7 +2922,6 @@
         private boolean mHeld;
         private WorkSource mWorkSource;
         private String mHistoryTag;
-        private final String mTraceName;
         private final int mDisplayId;
         private WakeLockStateListener mListener;
         private IWakeLockCallback mCallback;
@@ -2931,9 +2931,9 @@
         WakeLock(int flags, String tag, String packageName, int displayId) {
             mFlags = flags;
             mTag = tag;
+            mTagHash = mTag.hashCode();
             mPackageName = packageName;
             mToken = new Binder();
-            mTraceName = "WakeLock (" + mTag + ")";
             mDisplayId = displayId;
         }
 
@@ -2942,7 +2942,8 @@
             synchronized (mToken) {
                 if (mHeld) {
                     Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
-                    Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+                            "WakeLocks", mTagHash);
                     try {
                         mService.releaseWakeLock(mToken, 0);
                     } catch (RemoteException e) {
@@ -3012,7 +3013,8 @@
                 // should immediately acquire the wake lock once again despite never having
                 // been explicitly released by the keyguard.
                 mHandler.removeCallbacks(mReleaser);
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_POWER,
+                        "WakeLocks", mTag, mTagHash);
                 try {
                     mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                             mHistoryTag, mDisplayId, mCallback);
@@ -3060,7 +3062,8 @@
                 if (!mRefCounted || mInternalCount == 0) {
                     mHandler.removeCallbacks(mReleaser);
                     if (mHeld) {
-                        Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+                                "WakeLocks", mTagHash);
                         try {
                             mService.releaseWakeLock(mToken, flags);
                         } catch (RemoteException e) {
@@ -3137,6 +3140,7 @@
         /** @hide */
         public void setTag(String tag) {
             mTag = tag;
+            mTagHash = mTag.hashCode();
         }
 
         /** @hide */
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3..e483328 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -26,6 +26,7 @@
 import android.annotation.UptimeMillisLong;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build.VERSION_CODES;
+import android.sysprop.MemoryProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -1330,6 +1331,24 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public static final native void sendSignalQuiet(int pid, int signal);
 
+    /**
+     * @return The advertised memory of the system, as the end user would encounter in a retail
+     * display environment. If the advertised memory is not defined, it returns
+     * {@code getTotalMemory()} rounded.
+     *
+     * @hide
+     */
+    public static final long getAdvertisedMem() {
+        String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
+        long memSize = FileUtils.parseSize(formatSize);
+
+        if (memSize == Long.MIN_VALUE) {
+            return FileUtils.roundStorageSize(getTotalMemory());
+        }
+
+        return memSize;
+    }
+
     /** @hide */
     @UnsupportedAppUsage
     public static final native long getFreeMemory();
@@ -1463,6 +1482,18 @@
     public static final native int killProcessGroup(int uid, int pid);
 
     /**
+      * Freeze the cgroup for the given UID.
+      * This cgroup may contain child cgroups which will also be frozen. If this cgroup or its
+      * children contain processes with Binder interfaces, those interfaces should be frozen before
+      * the cgroup to avoid blocking synchronous callers indefinitely.
+      *
+      * @param uid The UID to be frozen
+      * @param freeze true = freeze; false = unfreeze
+      * @hide
+      */
+    public static final native void freezeCgroupUid(int uid, boolean freeze);
+
+    /**
      * Remove all process groups.  Expected to be called when ActivityManager
      * is restarted.
      * @hide
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 4965057..8bfa0e9 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -340,9 +340,6 @@
      * @hide
      */
     public static void instant(long traceTag, String methodName) {
-        if (methodName == null) {
-            throw new IllegalArgumentException("methodName cannot be null");
-        }
         if (isTagEnabled(traceTag)) {
             nativeInstant(traceTag, methodName);
         }
@@ -357,12 +354,6 @@
      * @hide
      */
     public static void instantForTrack(long traceTag, String trackName, String methodName) {
-        if (trackName == null) {
-            throw new IllegalArgumentException("trackName cannot be null");
-        }
-        if (methodName == null) {
-            throw new IllegalArgumentException("methodName cannot be null");
-        }
         if (isTagEnabled(traceTag)) {
             nativeInstantForTrack(traceTag, trackName, methodName);
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5c809a1..51dc643 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2869,10 +2869,10 @@
      * It includes:
      *
      * <ol>
-     *   <li>The current foreground user in the main display.
-     *   <li>Current background users in secondary displays (for example, passenger users on
-     *   automotive, using the display associated with their seats).
-     *   <li>Profile users (in the running / started state) of other visible users.
+     *   <li>The current foreground user.
+     *   <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).
      * </ol>
      *
      * @return whether the user is visible at the moment, as defined above.
@@ -4993,7 +4993,7 @@
     }
 
     /**
-     * Removes a user and all associated data.
+     * Removes a user and its profiles along with their associated data.
      * @param userId the integer handle of the user.
      * @hide
      */
@@ -5009,7 +5009,7 @@
     }
 
     /**
-     * Removes a user and all associated data.
+     * Removes a user and its profiles along with their associated data.
      *
      * @param user the user that needs to be removed.
      * @return {@code true} if the user was successfully removed, {@code false} otherwise.
@@ -5046,9 +5046,9 @@
     }
 
     /**
-     * Immediately removes the user or, if the user cannot be removed, such as when the user is
-     * the current user, then set the user as ephemeral so that it will be removed when it is
-     * stopped.
+     * Immediately removes the user and its profiles or, if the user cannot be removed, such as
+     * when the user is the current user, then set the user as ephemeral
+     * so that it will be removed when it is stopped.
      *
      * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has
      * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index e899f77..65528e3 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -128,7 +128,7 @@
         mNames = in.createStringArray();
 
         int numChains = in.readInt();
-        if (numChains > 0) {
+        if (numChains >= 0) {
             mChains = new ArrayList<>(numChains);
             in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class);
         } else {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index df0bee7..bc52744 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -137,6 +137,7 @@
     void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserKey(int userId) = 62;
+    @EnforcePermission("STORAGE_INTERNAL")
     void unlockUserKey(int userId, int serialNumber, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
     void lockUserKey(int userId) = 64;
@@ -146,9 +147,7 @@
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
-    void addUserKeyAuth(int userId, int serialNumber, in byte[] secret) = 70;
-    @EnforcePermission("STORAGE_INTERNAL")
-    void fixateNewestUserKeyAuth(int userId) = 71;
+    void setUserKeyProtection(int userId, in byte[] secret) = 70;
     @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
     void fstrim(int flags, IVoldTaskListener listener) = 72;
     AppFuseMount mountProxyFileDescriptorBridge() = 73;
@@ -165,8 +164,6 @@
     @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
     boolean needsCheckpoint() = 86;
     void abortChanges(in String message, boolean retry) = 87;
-    @EnforcePermission("STORAGE_INTERNAL")
-    void clearUserKeyAuth(int userId, int serialNumber, in byte[] secret) = 88;
     void fixupAppDir(in String path) = 89;
     void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90;
     PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 91;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index c1606e8..38ac984 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1606,15 +1606,6 @@
     }
 
     /** {@hide} */
-    public void unlockUserKey(int userId, int serialNumber, byte[] secret) {
-        try {
-            mStorageManager.unlockUserKey(userId, serialNumber, secret);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** {@hide} */
     public void lockUserKey(int userId) {
         try {
             mStorageManager.lockUserKey(userId);
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
index 7edc987..fe53d4e 100644
--- a/core/java/android/preference/GenericInflater.java
+++ b/core/java/android/preference/GenericInflater.java
@@ -406,7 +406,7 @@
             InflateException ie = new InflateException(attrs
                     .getPositionDescription()
                     + ": Error inflating class "
-                    + constructor.getClass().getName());
+                    + constructor.getDeclaringClass().getName());
             ie.initCause(e);
             throw ie;
         }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 345486e..7095d1b 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -377,6 +377,14 @@
     public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness";
 
     /**
+     * Namespace for Remote Key Provisioning related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
+            "remote_key_provisioning_native";
+
+    /**
      * Namespace for Rollback flags that are applied immediately.
      *
      * @hide
@@ -808,6 +816,13 @@
     @SystemApi
     public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore";
 
+    /**
+     * Namespace for ARC App Compat related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cab6acb..00633a2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -393,6 +393,21 @@
             "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of accessibility color and motion.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ACCESSIBILITY_COLOR_MOTION_SETTINGS =
+            "android.settings.ACCESSIBILITY_COLOR_MOTION_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of Reduce Bright Colors.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -438,6 +453,21 @@
             "android.settings.COLOR_INVERSION_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of text reading.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_TEXT_READING_SETTINGS =
+            "android.settings.TEXT_READING_SETTINGS";
+
+    /**
      * Activity Action: Show settings to control access to usage information.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 913efbd..194e1b5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4316,13 +4316,25 @@
          * subscription and while is in voice call.
          *
          * Default value is empty string.
-         *
+         * @deprecated This column is no longer supported. Use
+         * {@link #COLUMN_ENABLED_MOBILE_DATA_POLICIES} instead.
          * @hide
          */
+        @Deprecated
         public static final String COLUMN_DATA_ENABLED_OVERRIDE_RULES =
                 "data_enabled_override_rules";
 
         /**
+         * TelephonyProvider column name enabled_mobile_data_policies.
+         * A list of mobile data policies, each of which represented by an integer and joint by ",".
+         *
+         * Default value is empty string.
+         * @hide
+         */
+        public static final String COLUMN_ENABLED_MOBILE_DATA_POLICIES =
+                "enabled_mobile_data_policies";
+
+        /**
          * TelephonyProvider column name for user displayed name.
          * <P>Type: TEXT (String)</P>
          *
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 8efc5eb..e720f1a 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -65,6 +65,7 @@
     public static final int KM_TAG_PADDING = Tag.PADDING; // KM_ENUM_REP | 6;
     public static final int KM_TAG_CALLER_NONCE = Tag.CALLER_NONCE; // KM_BOOL | 7;
     public static final int KM_TAG_MIN_MAC_LENGTH = Tag.MIN_MAC_LENGTH; // KM_UINT | 8;
+    public static final int KM_TAG_EC_CURVE = Tag.EC_CURVE; // KM_ENUM | 10;
 
     public static final int KM_TAG_RSA_PUBLIC_EXPONENT = Tag.RSA_PUBLIC_EXPONENT; // KM_ULONG | 200;
     public static final int KM_TAG_INCLUDE_UNIQUE_ID = Tag.INCLUDE_UNIQUE_ID; // KM_BOOL | 202;
diff --git a/core/java/android/service/cloudsearch/CloudSearchService.java b/core/java/android/service/cloudsearch/CloudSearchService.java
index 5efa1ac..0ce9689 100644
--- a/core/java/android/service/cloudsearch/CloudSearchService.java
+++ b/core/java/android/service/cloudsearch/CloudSearchService.java
@@ -15,25 +15,14 @@
  */
 package android.service.cloudsearch;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.Service;
-import android.app.cloudsearch.ICloudSearchManager;
 import android.app.cloudsearch.SearchRequest;
 import android.app.cloudsearch.SearchResponse;
-import android.content.Context;
 import android.content.Intent;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.cloudsearch.ICloudSearchService.Stub;
-import android.util.Log;
-import android.util.Slog;
 
 /**
  * A service for returning search results from cloud services in response to an on device query.
@@ -72,39 +61,18 @@
      */
     public static final String SERVICE_INTERFACE =
             "android.service.cloudsearch.CloudSearchService";
-    private static final boolean DEBUG = false;
-    private static final String TAG = "CloudSearchService";
-    private Handler mHandler;
-    private ICloudSearchManager mService;
-
-    private final android.service.cloudsearch.ICloudSearchService mInterface = new Stub() {
-        @Override
-        public void onSearch(SearchRequest request) {
-            mHandler.sendMessage(
-                    obtainMessage(CloudSearchService::onSearch,
-                    CloudSearchService.this, request));
-        }
-    };
 
     @CallSuper
     @Override
     public void onCreate() {
         super.onCreate();
-        if (DEBUG) {
-            Log.d(TAG, "onCreate CloudSearchService");
-        }
-        mHandler = new Handler(Looper.getMainLooper(), null, true);
-
-        IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE);
-        mService = android.app.cloudsearch.ICloudSearchManager.Stub.asInterface(b);
     }
 
     /**
      * onSearch receives the input request, retrievals the search provider's own
      * corpus and returns the search response through returnResults below.
      *
-     *@param request the search request passed from the client.
-     *
+     * @param request the search request passed from the client.
      */
     public abstract void onSearch(@NonNull SearchRequest request);
 
@@ -112,30 +80,16 @@
      * returnResults returnes the response and its associated requestId, where
      * requestIs is generated by request through getRequestId().
      *
-     *@param requestId the request ID got from request.getRequestId().
-     *@param response the search response returned from the search provider.
-     *
+     * @param requestId the request ID got from request.getRequestId().
+     * @param response  the search response returned from the search provider.
      */
     public final void returnResults(@NonNull String requestId,
-                                    @NonNull SearchResponse response) {
-        try {
-            mService.returnResults(mInterface.asBinder(), requestId, response);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+            @NonNull SearchResponse response) {
     }
 
     @Override
     @NonNull
     public final IBinder onBind(@NonNull Intent intent) {
-        if (DEBUG) {
-            Log.d(TAG, "onBind CloudSearchService");
-        }
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
-        }
-        Slog.w(TAG, "Tried to bind to wrong intent (should be "
-                + SERVICE_INTERFACE + ": " + intent);
         return null;
     }
 }
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
new file mode 100644
index 0000000..186b2a6
--- /dev/null
+++ b/core/java/android/service/credentials/Action.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * An action defined by the provider that intents into the provider's app for specific
+ * user actions.
+ *
+ * @hide
+ */
+public final class Action implements Parcelable {
+    /** Info to be displayed with this action on the UI. */
+    private final @NonNull Slice mInfo;
+    /**
+     * The pending intent to be invoked when the user selects this action.
+     */
+    private final @NonNull PendingIntent mPendingIntent;
+
+    /**
+     * Constructs an action to be displayed on the UI.
+     *
+     * @param actionInfo The info to be displayed along with this action.
+     * @param pendingIntent The intent to be invoked when the user selects this action.
+     * @throws NullPointerException If {@code actionInfo}, or {@code pendingIntent} is null.
+     */
+    public Action(@NonNull Slice actionInfo, @NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(actionInfo, "actionInfo must not be null");
+        Objects.requireNonNull(pendingIntent, "pendingIntent must not be null");
+        mInfo = actionInfo;
+        mPendingIntent = pendingIntent;
+    }
+
+    private Action(@NonNull Parcel in) {
+        mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+                PendingIntent.class);
+    }
+
+    public static final @NonNull Creator<Action> CREATOR = new Creator<Action>() {
+        @Override
+        public Action createFromParcel(@NonNull Parcel in) {
+            return new Action(in);
+        }
+
+        @Override
+        public Action[] newArray(int size) {
+            return new Action[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mInfo.writeToParcel(dest, flags);
+        mPendingIntent.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Returns the action info as a {@link Slice} object, to be displayed on the UI.
+     */
+    public @NonNull Slice getActionInfo() {
+        return mInfo;
+    }
+
+    /**
+     * Returns the {@link PendingIntent} to be invoked when the action is selected.
+     */
+    public @NonNull PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialCallback.java b/core/java/android/service/credentials/CreateCredentialCallback.java
new file mode 100644
index 0000000..6108eea
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Callback to be invoked as a response to {@link CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialCallback {
+    private static final String TAG = "CreateCredentialCallback";
+
+    private final ICreateCredentialCallback mCallback;
+
+    /** @hide */
+    public CreateCredentialCallback(@NonNull ICreateCredentialCallback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Invoked on a successful response for {@link CreateCredentialRequest}
+     * @param response The response from the credential provider.
+     */
+    public void onSuccess(@NonNull CreateCredentialResponse response) {
+        try {
+            mCallback.onSuccess(response);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Invoked on a failure response for {@link CreateCredentialRequest}
+     * @param errorCode The code defining the type of error.
+     * @param message The message corresponding to the failure.
+     */
+    public void onFailure(int errorCode, @Nullable CharSequence message) {
+        Log.w(TAG, "onFailure: " + message);
+        try {
+            mCallback.onFailure(errorCode, message);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
new file mode 100644
index 0000000..eb7fba9
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
new file mode 100644
index 0000000..ac11e04b
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for creating a credential.
+ *
+ * @hide
+ */
+public final class CreateCredentialRequest implements Parcelable {
+    private final @NonNull String mCallingPackage;
+    private final @NonNull String mType;
+    private final @NonNull Bundle mData;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+     * null or empty.
+     * @throws NullPointerException If {@code data} is null.
+     */
+    public CreateCredentialRequest(@NonNull String callingPackage,
+            @NonNull String type, @NonNull Bundle data) {
+        mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+                "callingPackage must not be null or empty");
+        mType = Preconditions.checkStringNotEmpty(type,
+                "type must not be null or empty");
+        mData = Objects.requireNonNull(data, "data must not be null");
+    }
+
+    private CreateCredentialRequest(@NonNull Parcel in) {
+        mCallingPackage = in.readString8();
+        mType = in.readString8();
+        mData = in.readBundle();
+    }
+
+    public static final @NonNull Creator<CreateCredentialRequest> CREATOR =
+            new Creator<CreateCredentialRequest>() {
+                @Override
+                public CreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+                    return new CreateCredentialRequest(in);
+                }
+
+                @Override
+                public CreateCredentialRequest[] newArray(int size) {
+                    return new CreateCredentialRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mCallingPackage);
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+    }
+
+    /** Returns the calling package of the calling app. */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /** Returns the type of the credential to be created. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /** Returns the data to be used while creating the credential. */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/core/java/android/service/credentials/CreateCredentialResponse.aidl
similarity index 74%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
rename to core/java/android/service/credentials/CreateCredentialResponse.aidl
index f9b0800..73c9147 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ b/core/java/android/service/credentials/CreateCredentialResponse.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker.pip
+package android.service.credentials;
 
-internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
+parcelable CreateCredentialResponse;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
new file mode 100644
index 0000000..f2ad7272
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -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 android.service.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialResponse implements Parcelable {
+    private final @Nullable CharSequence mHeader;
+    private final @NonNull List<SaveEntry> mSaveEntries;
+
+    private CreateCredentialResponse(@NonNull Parcel in) {
+        mHeader = in.readCharSequence();
+        mSaveEntries = in.createTypedArrayList(SaveEntry.CREATOR);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeCharSequence(mHeader);
+        dest.writeTypedList(mSaveEntries);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
+            new Creator<CreateCredentialResponse>() {
+                @Override
+                public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+                    return new CreateCredentialResponse(in);
+                }
+
+                @Override
+                public CreateCredentialResponse[] newArray(int size) {
+                    return new CreateCredentialResponse[size];
+                }
+            };
+
+    /* package-private */ CreateCredentialResponse(
+            @Nullable CharSequence header,
+            @NonNull List<SaveEntry> saveEntries) {
+        this.mHeader = header;
+        this.mSaveEntries = saveEntries;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSaveEntries);
+    }
+
+    /** Returns the header to be displayed on the UI. */
+    public @Nullable CharSequence getHeader() {
+        return mHeader;
+    }
+
+    /** Returns the list of save entries to be displayed on the UI. */
+    public @NonNull List<SaveEntry> getSaveEntries() {
+        return mSaveEntries;
+    }
+
+    /**
+     * A builder for {@link CreateCredentialResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+
+        private @Nullable CharSequence mHeader;
+        private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+
+        /** Sets the header to be displayed on the UI. */
+        public @NonNull Builder setHeader(@Nullable CharSequence header) {
+            mHeader = header;
+            return this;
+        }
+
+        /**
+         * Sets the list of save entries to be shown on the UI.
+         *
+         * @throws IllegalArgumentException If {@code saveEntries} is empty.
+         * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
+         * are null.
+         */
+        public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+            Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
+            mSaveEntries = Preconditions.checkCollectionElementsNotNull(
+                    saveEntries, "saveEntries");
+            return this;
+        }
+
+        /**
+         * Adds an entry to the list of save entries to be shown on the UI.
+         *
+         * @throws NullPointerException If {@code saveEntry} is null.
+         */
+        public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
+            mSaveEntries.add(Objects.requireNonNull(saveEntry));
+            return this;
+        }
+
+        /**
+         * Builds the instance.
+         *
+         * @throws IllegalArgumentException If {@code saveEntries} is empty.
+         */
+        public @NonNull CreateCredentialResponse build() {
+            Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
+                    + "not be empty");
+            return new CreateCredentialResponse(
+                    mHeader,
+                    mSaveEntries);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/Credential.java b/core/java/android/service/credentials/Credential.java
new file mode 100644
index 0000000..7d5da8a
--- /dev/null
+++ b/core/java/android/service/credentials/Credential.java
@@ -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 android.service.credentials;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import static java.util.Objects.requireNonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A Credential object that contains type specific data that is returned from the credential
+ * provider to the framework. Framework then converts it to an app facing representation and
+ * returns to the calling app.
+ *
+ * @hide
+ */
+public final class Credential implements Parcelable {
+    /** The type of this credential. */
+    private final @NonNull String mType;
+
+    /** The data associated with this credential. */
+    private final @NonNull Bundle mData;
+
+    /**
+     * Constructs a credential object.
+     *
+     * @param type The type of the credential.
+     * @param data The data of the credential that is passed back to the framework, and eventually
+     *             to the calling app.
+     * @throws NullPointerException If {@code data} is null.
+     * @throws IllegalArgumentException If {@code type} is null or empty.
+     */
+    public Credential(@NonNull String type, @NonNull Bundle data) {
+        Preconditions.checkStringNotEmpty(type, "type must not be null, or empty");
+        requireNonNull(data, "data must not be null");
+        this.mType = type;
+        this.mData = data;
+    }
+
+    private Credential(@NonNull Parcel in) {
+        mType = in.readString16NoHelper();
+        mData = in.readBundle();
+    }
+
+    /**
+     * Returns the type of the credential.
+     */
+    public @NonNull String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the data associated with the credential.
+     */
+    public @NonNull Bundle getData() {
+        return mData;
+    }
+
+    public static final @NonNull Creator<Credential> CREATOR = new Creator<Credential>() {
+        @Override
+        public Credential createFromParcel(@NonNull Parcel in) {
+            return new Credential(in);
+        }
+
+        @Override
+        public Credential[] newArray(int size) {
+            return new Credential[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+    }
+}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
new file mode 100644
index 0000000..b49215a
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A credential entry that is displayed on the account selector UI. Each entry corresponds to
+ * something that the user can select.
+ *
+ * @hide
+ */
+public final class CredentialEntry implements Parcelable {
+    /** The type of the credential entry to be shown on the UI. */
+    private final @NonNull String mType;
+
+    /** The info to be displayed along with this credential entry on the UI. */
+    private final @NonNull Slice mInfo;
+
+    /** The pending intent to be invoked when this credential entry is selected. */
+    private final @Nullable PendingIntent mPendingIntent;
+
+    /**
+     * The underlying credential to be returned to the app when the user selects
+     * this credential entry.
+     */
+    private final @Nullable Credential mCredential;
+
+    /** A flag denoting whether auto-select is enabled for this entry. */
+    private final @NonNull boolean mAutoSelectAllowed;
+
+    private CredentialEntry(@NonNull String type, @NonNull Slice entryInfo,
+            @Nullable PendingIntent pendingIntent, @Nullable Credential credential,
+            @NonNull boolean autoSeletAllowed) {
+        mType = type;
+        mInfo = entryInfo;
+        mPendingIntent = pendingIntent;
+        mCredential = credential;
+        mAutoSelectAllowed = autoSeletAllowed;
+    }
+
+    private CredentialEntry(@NonNull Parcel in) {
+        mType = in.readString();
+        mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+                PendingIntent.class);
+        mCredential = in.readParcelable(Credential.class.getClassLoader(),
+                Credential.class);
+        mAutoSelectAllowed = in.readBoolean();
+    }
+
+    public static final @NonNull Creator<CredentialEntry> CREATOR =
+            new Creator<CredentialEntry>() {
+        @Override
+        public CredentialEntry createFromParcel(@NonNull Parcel in) {
+            return new CredentialEntry(in);
+        }
+
+        @Override
+        public CredentialEntry[] newArray(int size) {
+            return new CredentialEntry[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        mInfo.writeToParcel(dest, flags);
+        mPendingIntent.writeToParcel(dest, flags);
+        mCredential.writeToParcel(dest, flags);
+        dest.writeBoolean(mAutoSelectAllowed);
+    }
+
+    /**
+     * Returns the specific credential type of the entry.
+     */
+    public @NonNull String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the UI info to be displayed for this entry.
+     */
+    public @NonNull Slice getInfo() {
+        return mInfo;
+    }
+
+    /**
+     * Returns the pending intent to be invoked if the user selects this entry.
+     */
+    public @Nullable PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /**
+     * Returns the credential associated with this entry.
+     */
+    public @Nullable Credential getCredential() {
+        return mCredential;
+    }
+
+    /**
+     * Returns whether this entry can be auto selected if it is the only option for the user.
+     */
+    public @NonNull boolean isAutoSelectAllowed() {
+        return mAutoSelectAllowed;
+    }
+
+    /**
+     * Builder for {@link CredentialEntry}.
+     */
+    public static final class Builder {
+        private String mType;
+        private Slice mInfo;
+        private PendingIntent mPendingIntent;
+        private Credential mCredential;
+        private boolean mAutoSelectAllowed = false;
+
+        /**
+         * Builds the instance.
+         * @param type The type of credential underlying this credential entry.
+         * @param info The info to be displayed with this entry on the UI.
+         *
+         * @throws IllegalArgumentException If {@code type} is null or empty.
+         * @throws NullPointerException If {@code info} is null.
+         */
+        public Builder(@NonNull String type, @NonNull Slice info) {
+            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+                    + "null, or empty");
+            mInfo = Objects.requireNonNull(info, "info must not be null");
+        }
+
+        /**
+         * Sets the pendingIntent to be invoked if the user selects this entry.
+         *
+         * @throws IllegalStateException If {@code credential} is already set. Must either set the
+         * {@code credential}, or the {@code pendingIntent}.
+         */
+        public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+            Preconditions.checkState(pendingIntent != null && mCredential != null,
+                    "credential is already set. Cannot set both the pendingIntent "
+                            + "and the credential");
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Sets the credential to be used, if the user selects this entry.
+         *
+         * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
+         * the {@code pendingIntent}, or the {@code credential}.
+         */
+        public @NonNull Builder setCredential(@Nullable Credential credential) {
+            Preconditions.checkState(credential != null && mPendingIntent != null,
+                    "pendingIntent is already set. Cannot set both the "
+                            + "pendingIntent and the credential");
+            mCredential = credential;
+            return this;
+        }
+
+        /**
+         * Sets whether the entry is allowed to be auto selected by the framework.
+         * The default value is set to false.
+         */
+        public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
+            mAutoSelectAllowed = autoSelectAllowed;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link CredentialEntry} instance.
+         *
+         * @throws NullPointerException If {@code info} is null.
+         * @throws IllegalArgumentException If {@code type} is null, or empty.
+         * @throws IllegalStateException If neither {@code pendingIntent} nor {@code credential}
+         * is set, or if both are set.
+         */
+        public @NonNull CredentialEntry build() {
+            Preconditions.checkState(mPendingIntent == null && mCredential == null,
+                    "Either pendingIntent or credential must be set");
+            Preconditions.checkState(mPendingIntent != null && mCredential != null,
+                    "Cannot set both the pendingIntent and credential");
+            return new CredentialEntry(mType, mInfo, mPendingIntent,
+                    mCredential, mAutoSelectAllowed);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
new file mode 100644
index 0000000..1fe89df
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialProviderService.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 android.service.credentials;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.app.Service;
+import android.content.Intent;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Main service to be extended by credential providers, in order to return user credentials
+ * to the framework.
+ *
+ * @hide
+ */
+public abstract class CredentialProviderService extends Service {
+    private static final String TAG = "CredProviderService";
+    private Handler mHandler;
+
+    public static final String SERVICE_INTERFACE =
+            "android.service.credentials.CredentialProviderService";
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    public final @NonNull IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.i(TAG, "Failed to bind with intent: " + intent);
+        return null;
+    }
+
+    private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
+        @Override
+        public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
+                IGetCredentialsCallback callback) throws RemoteException {
+            Objects.requireNonNull(request);
+            Objects.requireNonNull(transport);
+            Objects.requireNonNull(callback);
+
+            mHandler.sendMessage(obtainMessage(
+                    CredentialProviderService::onGetCredentials,
+                    CredentialProviderService.this, request,
+                    CancellationSignal.fromTransport(transport),
+                    new GetCredentialsCallback(callback)
+            ));
+        }
+
+        @Override
+        public void onCreateCredential(CreateCredentialRequest request,
+                ICancellationSignal transport, ICreateCredentialCallback callback)
+                throws RemoteException {
+            Objects.requireNonNull(request);
+            Objects.requireNonNull(transport);
+            Objects.requireNonNull(callback);
+
+            mHandler.sendMessage(obtainMessage(
+                    CredentialProviderService::onCreateCredential,
+                    CredentialProviderService.this, request,
+                    CancellationSignal.fromTransport(transport),
+                    new CreateCredentialCallback(callback)
+            ));
+        }
+    };
+
+    /**
+     * Called by the android system to retrieve user credentials from the connected provider
+     * service.
+     * @param request The credential request for the provider to handle.
+     * @param cancellationSignal Signal for providers to listen to any cancellation requests from
+     *                           the android system.
+     * @param callback Object used to relay the response of the credentials request.
+     */
+    public abstract void onGetCredentials(@NonNull GetCredentialsRequest request,
+            @NonNull CancellationSignal cancellationSignal,
+            @NonNull GetCredentialsCallback callback);
+
+    /**
+     * Called by the android system to create a credential.
+     * @param request The credential creation request for the provider to handle.
+     * @param cancellationSignal Signal for providers to listen to any cancellation requests from
+     *                           the android system.
+     * @param callback Object used to relay the response of the credential creation request.
+     */
+    public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+            @NonNull CancellationSignal cancellationSignal,
+            @NonNull CreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
new file mode 100644
index 0000000..106f322
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Content to be displayed on the account selector UI, including credential entries,
+ * actions etc.
+ *
+ * @hide
+ */
+public final class CredentialsDisplayContent implements Parcelable {
+    /** Header to be displayed on the UI. */
+    private final @Nullable CharSequence mHeader;
+
+    /** List of credential entries to be displayed on the UI. */
+    private final @NonNull List<CredentialEntry> mCredentialEntries;
+
+    /** List of provider actions to be displayed on the UI. */
+    private final @NonNull List<Action> mActions;
+
+    private CredentialsDisplayContent(@Nullable CharSequence header,
+            @NonNull List<CredentialEntry> credentialEntries,
+            @NonNull List<Action> actions) {
+        mHeader = header;
+        mCredentialEntries = credentialEntries;
+        mActions = actions;
+    }
+
+    private CredentialsDisplayContent(@NonNull Parcel in) {
+        mHeader = in.readCharSequence();
+        mCredentialEntries = in.createTypedArrayList(CredentialEntry.CREATOR);
+        mActions = in.createTypedArrayList(Action.CREATOR);
+    }
+
+    public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
+            new Creator<CredentialsDisplayContent>() {
+                @Override
+                public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
+                    return new CredentialsDisplayContent(in);
+                }
+
+                @Override
+                public CredentialsDisplayContent[] newArray(int size) {
+                    return new CredentialsDisplayContent[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeCharSequence(mHeader);
+        dest.writeTypedList(mCredentialEntries);
+        dest.writeTypedList(mActions);
+    }
+
+    /**
+     * Returns the header to be displayed on the UI.
+     */
+    public @Nullable CharSequence getHeader() {
+        return mHeader;
+    }
+
+    /**
+     * Returns the list of credential entries to be displayed on the UI.
+     */
+    public @NonNull List<CredentialEntry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    /**
+     * Returns the list of actions to be displayed on the UI.
+     */
+    public @NonNull List<Action> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Builds an instance of {@link CredentialsDisplayContent}.
+     */
+    public static final class Builder {
+        private CharSequence mHeader = null;
+        private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
+        private List<Action> mActions = new ArrayList<>();
+
+        /**
+         * Sets the header to be displayed on the UI.
+         */
+        public @NonNull Builder setHeader(@Nullable CharSequence header) {
+            mHeader = header;
+            return this;
+        }
+
+        /**
+         * Adds a {@link CredentialEntry} to the list of entries to be displayed on
+         * the UI.
+         *
+         * @throws NullPointerException If the {@code credentialEntry} is null.
+         */
+        public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
+            mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
+            return this;
+        }
+
+        /**
+         * Adds an {@link Action} to the list of actions to be displayed on
+         * the UI.
+         *
+         * @throws NullPointerException If {@code action} is null.
+         */
+        public @NonNull Builder addAction(@NonNull Action action) {
+            mActions.add(Objects.requireNonNull(action, "action must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets the list of actions to be displayed on the UI.
+         *
+         * @throws NullPointerException If {@code actions} is null, or any of its elements
+         * is null.
+         */
+        public @NonNull Builder setActions(@NonNull List<Action> actions) {
+            mActions = Preconditions.checkCollectionElementsNotNull(actions,
+                    "actions");
+            return this;
+        }
+
+        /**
+         * Sets the list of credential entries to be displayed on the
+         * account selector UI.
+         *
+         * @throws NullPointerException If {@code credentialEntries} is null, or any of its
+         * elements is null.
+         */
+        public @NonNull Builder setCredentialEntries(
+                @NonNull List<CredentialEntry> credentialEntries) {
+            mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
+                    credentialEntries,
+                    "credentialEntries");
+            return this;
+        }
+
+        /**
+         * Builds a {@link GetCredentialsResponse} instance.
+         *
+         * @throws NullPointerException If {@code credentialEntries} is null.
+         * @throws IllegalStateException if both {@code credentialEntries} and
+         * {@code actions} are empty.
+         */
+        public @NonNull CredentialsDisplayContent build() {
+            if (mCredentialEntries != null && mCredentialEntries.isEmpty()
+                    && mActions != null && mActions.isEmpty()) {
+                throw new IllegalStateException("credentialEntries and actions must not both "
+                        + "be empty");
+            }
+            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/GetCredentialOption.java b/core/java/android/service/credentials/GetCredentialOption.java
new file mode 100644
index 0000000..c6cda1d
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialOption.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A type specific credential request, containing the associated data to be used for
+ * retrieving credentials.
+ *
+ * @hide
+ */
+public final class GetCredentialOption implements Parcelable {
+    /** The type of credential requested. */
+    private final @NonNull String mType;
+
+    /** The data associated with the request. */
+    private final @NonNull Bundle mData;
+
+    /**
+     * Constructs a new instance of {@link GetCredentialOption}
+     *
+     * @throws IllegalArgumentException If {@code type} string is null or empty.
+     * @throws NullPointerException If {@code data} is null.
+     */
+    public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
+        Preconditions.checkStringNotEmpty(type, "type must not be null, or empty");
+        requireNonNull(data, "data must not be null");
+        mType = type;
+        mData = data;
+    }
+
+    /**
+     * Returns the data associated with this credential request option.
+     */
+    public @NonNull Bundle getData() {
+        return mData;
+    }
+
+    /**
+     * Returns the type associated with this credential request option.
+     */
+    public @NonNull String getType() {
+        return mType;
+    }
+
+    private GetCredentialOption(@NonNull Parcel in) {
+        mType = in.readString16NoHelper();
+        mData = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString16NoHelper(mType);
+        dest.writeBundle(mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<GetCredentialOption> CREATOR =
+            new Creator<GetCredentialOption>() {
+        @Override
+        public GetCredentialOption createFromParcel(@NonNull Parcel in) {
+            return new GetCredentialOption(in);
+        }
+
+        @Override
+        public GetCredentialOption[] newArray(int size) {
+            return new GetCredentialOption[size];
+        }
+    };
+}
diff --git a/core/java/android/service/credentials/GetCredentialsCallback.java b/core/java/android/service/credentials/GetCredentialsCallback.java
new file mode 100644
index 0000000..42a7394
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Callback to be invoked as a response to {@link GetCredentialsRequest}.
+ *
+ * @hide
+ */
+public final class GetCredentialsCallback {
+
+    private static final String TAG = "GetCredentialsCallback";
+
+    private final IGetCredentialsCallback mCallback;
+
+    /** @hide */
+    public GetCredentialsCallback(@NonNull IGetCredentialsCallback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Invoked on a successful response for {@link GetCredentialsRequest}
+     * @param response The response from the credential provider.
+     */
+    public void onSuccess(@NonNull GetCredentialsResponse response) {
+        try {
+            mCallback.onSuccess(response);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Invoked on a failure response for {@link GetCredentialsRequest}
+     * @param errorCode The code defining the kind of error.
+     * @param message The message corresponding to the failure.
+     */
+    public void onFailure(int errorCode, @Nullable CharSequence message) {
+        Log.w(TAG, "onFailure: " + message);
+        try {
+            mCallback.onFailure(errorCode, message);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.aidl b/core/java/android/service/credentials/GetCredentialsRequest.aidl
new file mode 100644
index 0000000..b309d69
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable GetCredentialsRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
new file mode 100644
index 0000000..cf7c283
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for getting user's credentials from a given credential provider.
+ *
+ * @hide
+ */
+public final class GetCredentialsRequest implements Parcelable {
+    /** Calling package of the app requesting for credentials. */
+    private final @NonNull String mCallingPackage;
+
+    /**
+     * List of credential options. Each {@link GetCredentialOption} object holds parameters to
+     * be used for retrieving specific type of credentials.
+     */
+    private final @NonNull List<GetCredentialOption> mGetCredentialOptions;
+
+    private GetCredentialsRequest(@NonNull String callingPackage,
+            @NonNull List<GetCredentialOption> getCredentialOptions) {
+        this.mCallingPackage = callingPackage;
+        this.mGetCredentialOptions = getCredentialOptions;
+    }
+
+    private GetCredentialsRequest(@NonNull Parcel in) {
+        mCallingPackage = in.readString16NoHelper();
+        mGetCredentialOptions = in.createTypedArrayList(GetCredentialOption.CREATOR);
+    }
+
+    public static final @NonNull Creator<GetCredentialsRequest> CREATOR =
+            new Creator<GetCredentialsRequest>() {
+                @Override
+                public GetCredentialsRequest createFromParcel(Parcel in) {
+                    return new GetCredentialsRequest(in);
+                }
+
+                @Override
+                public GetCredentialsRequest[] newArray(int size) {
+                    return new GetCredentialsRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString16NoHelper(mCallingPackage);
+        dest.writeTypedList(mGetCredentialOptions);
+    }
+
+    /**
+     * Returns the calling package of the app requesting credentials.
+     */
+    public @NonNull String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /**
+     * Returns the list of type specific credential options to return credentials for.
+     */
+    public @NonNull List<GetCredentialOption> getGetCredentialOptions() {
+        return mGetCredentialOptions;
+    }
+
+    /**
+     * Builder for {@link GetCredentialsRequest}.
+     */
+    public static final class Builder {
+        private String mCallingPackage;
+        private List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+
+        /**
+         * Creates a new builder.
+         * @param callingPackage The calling package of the app requesting credentials.
+         *
+         * @throws IllegalArgumentException If {@code callingPackag}e is null or empty.
+         */
+        public Builder(@NonNull String callingPackage) {
+            mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage);
+        }
+
+        /**
+         * Sets the list of credential options.
+         *
+         * @throws NullPointerException If {@code getCredentialOptions} itself or any of its
+         * elements is null.
+         * @throws IllegalArgumentException If {@code getCredentialOptions} is empty.
+         */
+        public @NonNull Builder setGetCredentialOptions(
+                @NonNull List<GetCredentialOption> getCredentialOptions) {
+            Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+                    "getCredentialOptions");
+            Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+                    "getCredentialOptions");
+            mGetCredentialOptions = getCredentialOptions;
+            return this;
+        }
+
+        /**
+         * Adds a single {@link GetCredentialOption} object to the list of credential options.
+         *
+         * @throws NullPointerException If {@code getCredentialOption} is null.
+         */
+        public @NonNull Builder addGetCredentialOption(
+                @NonNull GetCredentialOption getCredentialOption) {
+            Objects.requireNonNull(getCredentialOption,
+                    "getCredentialOption must not be null");
+            mGetCredentialOptions.add(getCredentialOption);
+            return this;
+        }
+
+        /**
+         * Builds a new {@link GetCredentialsRequest} instance.
+         *
+         * @throws NullPointerException If {@code getCredentialOptions} is null.
+         * @throws IllegalArgumentException If {@code getCredentialOptions} is empty, or if
+         * {@code callingPackage} is null or empty.
+         */
+        public @NonNull GetCredentialsRequest build() {
+            Preconditions.checkStringNotEmpty(mCallingPackage,
+                    "Must set the calling package");
+            Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+                    "getCredentialOptions");
+            return new GetCredentialsRequest(mCallingPackage, mGetCredentialOptions);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.aidl b/core/java/android/service/credentials/GetCredentialsResponse.aidl
new file mode 100644
index 0000000..0d8c635
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsResponse.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable GetCredentialsResponse;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
new file mode 100644
index 0000000..293867b
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -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 android.service.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Response from a credential provider, containing credential entries and other associated
+ * data to be shown on the account selector UI.
+ *
+ * @hide
+ */
+public final class GetCredentialsResponse implements Parcelable {
+    /** Content to be used for the UI. */
+    private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+
+    /**
+     * Authentication action that must be launched and completed before showing any content
+     * from the provider.
+     */
+    private final @Nullable Action mAuthenticationAction;
+
+    /**
+     * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+     * Providers must use this method when no content can be shown before authentication.
+     *
+     * @throws NullPointerException If {@code authenticationAction} is null.
+     */
+    public static @NonNull GetCredentialsResponse createWithAuthentication(
+            @NonNull Action authenticationAction) {
+        Objects.requireNonNull(authenticationAction,
+                "authenticationAction must not be null");
+        return new GetCredentialsResponse(null, authenticationAction);
+    }
+
+    /**
+     * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+     * Providers must use this method when there is content to be shown without top level
+     * authentication required.
+     *
+     * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+     */
+    public static @NonNull GetCredentialsResponse createWithDisplayContent(
+            @NonNull CredentialsDisplayContent credentialsDisplayContent) {
+        Objects.requireNonNull(credentialsDisplayContent,
+                "credentialsDisplayContent must not be null");
+        return new GetCredentialsResponse(credentialsDisplayContent, null);
+    }
+
+    private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+            @Nullable Action authenticationAction) {
+        mCredentialsDisplayContent = credentialsDisplayContent;
+        mAuthenticationAction = authenticationAction;
+    }
+
+    private GetCredentialsResponse(@NonNull Parcel in) {
+        mCredentialsDisplayContent = in.readParcelable(CredentialsDisplayContent.class
+                .getClassLoader(), CredentialsDisplayContent.class);
+        mAuthenticationAction = in.readParcelable(Action.class.getClassLoader(), Action.class);
+    }
+
+    public static final @NonNull Creator<GetCredentialsResponse> CREATOR =
+            new Creator<GetCredentialsResponse>() {
+                @Override
+                public GetCredentialsResponse createFromParcel(Parcel in) {
+                    return new GetCredentialsResponse(in);
+                }
+
+                @Override
+                public GetCredentialsResponse[] newArray(int size) {
+                    return new GetCredentialsResponse[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mCredentialsDisplayContent, flags);
+        dest.writeParcelable(mAuthenticationAction, flags);
+    }
+
+    /**
+     * Returns whether the response contains a top level authentication action.
+     */
+    public @NonNull boolean isAuthenticationActionSet() {
+        return mAuthenticationAction != null;
+    }
+
+    /**
+     * Returns the authentication action to be invoked before any other content
+     * can be shown to the user.
+     */
+    public @NonNull Action getAuthenticationAction() {
+        return mAuthenticationAction;
+    }
+
+    /**
+     * Returns the credentialDisplayContent that does not require authentication, and
+     * can be shown to the user on the account selector UI.
+     */
+    public @NonNull CredentialsDisplayContent getCredentialsDisplayContent() {
+        return mCredentialsDisplayContent;
+    }
+}
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
new file mode 100644
index 0000000..4cc76a4
--- /dev/null
+++ b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.CreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface ICreateCredentialCallback {
+    void onSuccess(in CreateCredentialResponse request);
+    void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
new file mode 100644
index 0000000..c68430c
--- /dev/null
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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 android.service.credentials;
+
+import android.os.ICancellationSignal;
+import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.IGetCredentialsCallback;
+import android.service.credentials.ICreateCredentialCallback;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface ICredentialProviderService {
+    void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
+    void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/credentials/IGetCredentialsCallback.aidl b/core/java/android/service/credentials/IGetCredentialsCallback.aidl
new file mode 100644
index 0000000..6e20c55
--- /dev/null
+++ b/core/java/android/service/credentials/IGetCredentialsCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.GetCredentialsResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IGetCredentialsCallback {
+    void onSuccess(in GetCredentialsResponse response);
+    void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
new file mode 100644
index 0000000..28fec30
--- /dev/null
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.credentials;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * An entry to be shown on the UI. This entry represents where the credential to be created will
+ * be stored. Examples include user's account, family group etc.
+ *
+ * @hide
+ */
+public final class SaveEntry implements Parcelable {
+    private final @NonNull Slice mInfo;
+    private final @Nullable PendingIntent mPendingIntent;
+    private final @Nullable Credential mCredential;
+
+    private SaveEntry(@NonNull Parcel in) {
+        mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+                PendingIntent.class);
+        mCredential = in.readParcelable(Credential.class.getClassLoader(), Credential.class);
+    }
+
+    public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+        @Override
+        public SaveEntry createFromParcel(@NonNull Parcel in) {
+            return new SaveEntry(in);
+        }
+
+        @Override
+        public SaveEntry[] newArray(int size) {
+            return new SaveEntry[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mInfo.writeToParcel(dest, flags);
+        mPendingIntent.writeToParcel(dest, flags);
+        mCredential.writeToParcel(dest, flags);
+    }
+
+    /* package-private */ SaveEntry(
+            @NonNull Slice info,
+            @Nullable PendingIntent pendingIntent,
+            @Nullable Credential credential) {
+        this.mInfo = info;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInfo);
+        this.mPendingIntent = pendingIntent;
+        this.mCredential = credential;
+    }
+
+    /** Returns the info to be displayed with this save entry on the UI. */
+    public @NonNull Slice getInfo() {
+        return mInfo;
+    }
+
+    /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+    public @Nullable PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /** Returns the credential produced by the {@link CreateCredentialRequest}. */
+    public @Nullable Credential getCredential() {
+        return mCredential;
+    }
+
+    /**
+     * A builder for {@link SaveEntry}.
+     */
+    public static final class Builder {
+
+        private @NonNull Slice mInfo;
+        private @Nullable PendingIntent mPendingIntent;
+        private @Nullable Credential mCredential;
+
+        /**
+         * Builds the instance.
+         * @param info The info to be displayed with this save entry.
+         *
+         * @throws NullPointerException If {@code info} is null.
+         */
+        public Builder(@NonNull Slice info) {
+            mInfo = Objects.requireNonNull(info, "info must not be null");
+        }
+
+        /**
+         * Sets the pendingIntent to be invoked when this entry is selected by the user.
+         *
+         * @throws IllegalStateException If {@code credential} is already set. Must only set either
+         * {@code credential}, or the {@code pendingIntent}.
+         */
+        public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+            Preconditions.checkState(pendingIntent != null
+                    && mCredential != null, "credential is already set. Must only set "
+                    + "either the pendingIntent or the credential");
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Sets the credential to be returned when this entry is selected by the user.
+         *
+         * @throws IllegalStateException If {@code pendingIntent} is already set. Must only
+         * set either the {@code pendingIntent}, or {@code credential}.
+         */
+        public @NonNull Builder setCredential(@Nullable Credential credential) {
+            Preconditions.checkState(credential != null && mPendingIntent != null,
+                    "pendingIntent is already set. Must only set either the credential "
+                            + "or the pendingIntent");
+            mCredential = credential;
+            return this;
+        }
+
+        /**
+         * Builds the instance.
+         *
+         * @throws IllegalStateException if both {@code pendingIntent} and {@code credential}
+         * are null.
+         */
+        public @NonNull SaveEntry build() {
+            Preconditions.checkState(mPendingIntent == null && mCredential == null,
+                    "pendingIntent and credential both must not be null. Must set "
+                            + "either the pendingIntnet or the credential");
+            return new SaveEntry(
+                    mInfo,
+                    mPendingIntent,
+                    mCredential);
+        }
+    }
+}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 163d6ed..4324442 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.Service;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -36,6 +37,7 @@
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = false;
     private boolean mShowComplications;
+    private ComponentName mDreamComponent;
 
     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
         @Override
@@ -56,6 +58,8 @@
     public final IBinder onBind(@NonNull Intent intent) {
         mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
                 DreamService.DEFAULT_SHOW_COMPLICATIONS);
+        mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT,
+                ComponentName.class);
         return mDreamOverlay.asBinder();
     }
 
@@ -84,4 +88,12 @@
     public final boolean shouldShowComplications() {
         return mShowComplications;
     }
+
+    /**
+     * Returns the active dream component.
+     * @hide
+     */
+    public final ComponentName getDreamComponent() {
+        return mDreamComponent;
+    }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6f5212c..1391326 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -218,6 +218,12 @@
             "android.service.dreams.SHOW_COMPLICATIONS";
 
     /**
+     * Extra containing the component name for the active dream.
+     * @hide
+     */
+    public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT";
+
+    /**
      * The default value for whether to show complications on the overlay.
      * @hide
      */
@@ -271,6 +277,7 @@
             overlayIntent.setComponent(overlayService);
             overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
                     fetchShouldShowComplications(context, serviceInfo));
+            overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService);
 
             context.bindService(overlayIntent,
                     this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index fae72a2..ced2a01 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
-import com.android.server.LocalServices;
-
 /**
  * Internal helper for launching dreams to ensure consistency between the
  * <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity.
@@ -75,28 +75,32 @@
     }
 
     private static void startDream(Context context, boolean docked) {
-        DreamManagerInternal dreamManagerService =
-                LocalServices.getService(DreamManagerInternal.class);
-        if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
-            if (docked) {
-                Slog.i(TAG, "Activating dream while docked.");
+        try {
+            IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
+                    ServiceManager.getService(DreamService.DREAM_SERVICE));
+            if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
+                if (docked) {
+                    Slog.i(TAG, "Activating dream while docked.");
 
-                // Wake up.
-                // The power manager will wake up the system automatically when it starts
-                // receiving power from a dock but there is a race between that happening
-                // and the UI mode manager starting a dream.  We want the system to already
-                // be awake by the time this happens.  Otherwise the dream may not start.
-                PowerManager powerManager =
-                        context.getSystemService(PowerManager.class);
-                powerManager.wakeUp(SystemClock.uptimeMillis(),
-                        PowerManager.WAKE_REASON_PLUGGED_IN,
-                        "android.service.dreams:DREAM");
-            } else {
-                Slog.i(TAG, "Activating dream by user request.");
+                    // Wake up.
+                    // The power manager will wake up the system automatically when it starts
+                    // receiving power from a dock but there is a race between that happening
+                    // and the UI mode manager starting a dream.  We want the system to already
+                    // be awake by the time this happens.  Otherwise the dream may not start.
+                    PowerManager powerManager =
+                            context.getSystemService(PowerManager.class);
+                    powerManager.wakeUp(SystemClock.uptimeMillis(),
+                            PowerManager.WAKE_REASON_PLUGGED_IN,
+                            "android.service.dreams:DREAM");
+                } else {
+                    Slog.i(TAG, "Activating dream by user request.");
+                }
+
+                // Dream.
+                dreamManagerService.dream();
             }
-
-            // Dream.
-            dreamManagerService.requestDream();
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Could not start dream when docked.", ex);
         }
     }
 
diff --git a/core/java/android/service/resumeonreboot/OWNERS b/core/java/android/service/resumeonreboot/OWNERS
index 721fbaf..e098053 100644
--- a/core/java/android/service/resumeonreboot/OWNERS
+++ b/core/java/android/service/resumeonreboot/OWNERS
@@ -1 +1,2 @@
-ejyzhang@google.com
\ No newline at end of file
+aveena@google.com
+ejyzhang@google.com
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index c6de843..df69cc0 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -88,6 +88,14 @@
     public static final int MAXIMUM_NUMBER_OF_INITIALIZATION_STATUS_CUSTOM_ERROR = 2;
 
     /**
+     * Feature flag for Attention Service.
+     *
+     * TODO(b/247920386): Add TestApi annotation
+     * @hide
+     */
+    public static final boolean ENABLE_PROXIMITY_RESULT = false;
+
+    /**
      * Indicates that the updated status is successful.
      */
     public static final int INITIALIZATION_STATUS_SUCCESS = 0;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1b46107..1285d1e 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,6 +345,12 @@
      * Calling this a second time invalidates the previously created hotword detector
      * which can no longer be used to manage recognition.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+     * AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
+     * SharedMemory, HotwordDetector.Callback)}, call this will throw an
+     * {@link IllegalArgumentException}.
+     *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      * @param callback The callback to notify of detection events.
@@ -377,6 +383,10 @@
      * <p>Note: The system will trigger hotword detection service after calling this function when
      * all conditions meet the requirements.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+     * call this will throw an {@link IllegalArgumentException}.
+     *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      * @param options Application configuration data provided by the
@@ -420,6 +430,14 @@
                 safelyShutdownAllHotwordDetectors();
             }
 
+            for (HotwordDetector detector : mActiveHotwordDetectors) {
+                if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
+                    throw new IllegalArgumentException(
+                            "It disallows to create trusted and non-trusted detectors "
+                                    + "at the same time.");
+                }
+            }
+
             AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
                     callback, mKeyphraseEnrollmentInfo, mSystemService,
                     getApplicationContext().getApplicationInfo().targetSdkVersion,
@@ -460,6 +478,10 @@
      * devices where hardware filtering is available (such as through a DSP), it's highly
      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+     * call this will throw an {@link IllegalArgumentException}.
+     *
      * @param options Application configuration data to be provided to the
      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
      * other contents that can be used to communicate with other processes.
@@ -490,7 +512,11 @@
                 safelyShutdownAllHotwordDetectors();
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
-                    if (detector instanceof SoftwareHotwordDetector) {
+                    if (!detector.isUsingHotwordDetectionService()) {
+                        throw new IllegalArgumentException(
+                                "It disallows to create trusted and non-trusted detectors "
+                                        + "at the same time.");
+                    } else if (detector instanceof SoftwareHotwordDetector) {
                         throw new IllegalArgumentException(
                                 "There is already an active SoftwareHotwordDetector. "
                                         + "It must be destroyed to create a new one.");
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 82e1d5f0..2c70229 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -20,9 +20,11 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.Dialog;
 import android.app.DirectAction;
@@ -73,6 +75,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -146,6 +150,25 @@
      */
     public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
 
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = {
+            VOICE_INTERACTION_ACTIVITY_EVENT_START,
+            VOICE_INTERACTION_ACTIVITY_EVENT_RESUME,
+            VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE,
+            VOICE_INTERACTION_ACTIVITY_EVENT_STOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VoiceInteractionActivityEventType{}
+
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
@@ -2289,11 +2312,15 @@
             mAssistToken = assistToken;
         }
 
-        int getTaskId() {
+        /** @hide */
+        @TestApi
+        public int getTaskId() {
             return mTaskId;
         }
 
-        IBinder getAssistToken() {
+        /** @hide */
+        @TestApi
+        @NonNull public IBinder getAssistToken() {
             return mAssistToken;
         }
 
diff --git a/core/java/android/text/GraphemeClusterSegmentIterator.java b/core/java/android/text/GraphemeClusterSegmentFinder.java
similarity index 84%
rename from core/java/android/text/GraphemeClusterSegmentIterator.java
rename to core/java/android/text/GraphemeClusterSegmentFinder.java
index e3976a7..8b3db0a 100644
--- a/core/java/android/text/GraphemeClusterSegmentIterator.java
+++ b/core/java/android/text/GraphemeClusterSegmentFinder.java
@@ -21,16 +21,20 @@
 import android.graphics.Paint;
 
 /**
- * Implementation of {@code SegmentIterator} using grapheme clusters as the text segment. Whitespace
+ * Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace
  * characters are included as segments.
  *
- * @hide
+ * <p>For example, the text "a pot" would be divided into five text segments: "a", " ", "p", "o",
+ * "t".
+ *
+ * @see <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode Text
+ *     Segmentation - Grapheme Cluster Boundaries</a>
  */
-public class GraphemeClusterSegmentIterator extends SegmentIterator {
+public class GraphemeClusterSegmentFinder extends SegmentFinder {
     private final CharSequence mText;
     private final TextPaint mTextPaint;
 
-    public GraphemeClusterSegmentIterator(
+    public GraphemeClusterSegmentFinder(
             @NonNull CharSequence text, @NonNull TextPaint textPaint) {
         mText = text;
         mTextPaint = textPaint;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index b5f7c54..913e9cc 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -168,6 +168,31 @@
     public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
 
     /**
+     * Strategy which considers a text segment to be inside a rectangle area if the segment bounds
+     * intersect the rectangle.
+     */
+    @NonNull
+    public static final TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP =
+            RectF::intersects;
+
+    /**
+     * Strategy which considers a text segment to be inside a rectangle area if the center of the
+     * segment bounds is inside the rectangle.
+     */
+    @NonNull
+    public static final TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER =
+            (segmentBounds, area) ->
+                    area.contains(segmentBounds.centerX(), segmentBounds.centerY());
+
+    /**
+     * Strategy which considers a text segment to be inside a rectangle area if the segment bounds
+     * are completely contained within the rectangle.
+     */
+    @NonNull
+    public static final TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL =
+            (segmentBounds, area) -> area.contains(segmentBounds);
+
+    /**
      * Return how wide a layout must be in order to display the specified text with one line per
      * paragraph.
      *
@@ -1818,11 +1843,11 @@
      * is the start of the first text segment inside the area, and the end of the range is the end
      * of the last text segment inside the area.
      *
-     * <p>A text segment is considered to be inside the area if the center of its bounds is inside
-     * the area. If a text segment spans multiple lines or multiple directional runs (e.g. a
-     * hyphenated word), the text segment is divided into pieces at the line and run breaks, then
-     * the text segment is considered to be inside the area if any of its pieces have their center
-     * inside the area.
+     * <p>A text segment is considered to be inside the area according to the provided {@link
+     * TextInclusionStrategy}. If a text segment spans multiple lines or multiple directional runs
+     * (e.g. a hyphenated word), the text segment is divided into pieces at the line and run breaks,
+     * then the text segment is considered to be inside the area if any of its pieces are inside the
+     * area.
      *
      * <p>The returned range may also include text segments which are not inside the specified area,
      * if those text segments are in between text segments which are inside the area. For example,
@@ -1830,62 +1855,59 @@
      * inside the area and "segment2" is not.
      *
      * @param area area for which the text range will be found
-     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
-     *     segment
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *          specified area
      * @return int array of size 2 containing the start (inclusive) and end (exclusive) character
      *     offsets of the range, or null if there are no text segments inside the area
-     * @hide
      */
     @Nullable
-    public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentIterator segmentIterator) {
-        // Find the first line whose vertical center is below the top of the area.
+    public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
+            @NonNull TextInclusionStrategy inclusionStrategy) {
+        // Find the first line whose bottom (without line spacing) is below the top of the area.
         int startLine = getLineForVertical((int) area.top);
-        int startLineTop = getLineTop(startLine);
-        int startLineBottom = getLineBottomWithoutSpacing(startLine);
-        if (area.top > (startLineTop + startLineBottom) / 2f) {
+        if (area.top > getLineBottom(startLine, /* includeLineSpacing= */ false)) {
             startLine++;
             if (startLine >= getLineCount()) {
-                // The top of the area is below the vertical center of the last line, so the area
-                // does not contain any text.
+                // The entire area is below the last line, so it does not contain any text.
                 return null;
             }
         }
 
-        // Find the last line whose vertical center is above the bottom of the area.
+        // Find the last line whose top is above the bottom of the area.
         int endLine = getLineForVertical((int) area.bottom);
-        int endLineTop = getLineTop(endLine);
-        int endLineBottom = getLineBottomWithoutSpacing(endLine);
-        if (area.bottom < (endLineTop + endLineBottom) / 2f) {
-            endLine--;
+        if (endLine == 0 && area.bottom < getLineTop(0)) {
+            // The entire area is above the first line, so it does not contain any text.
+            return null;
         }
         if (endLine < startLine) {
-            // There are no lines with vertical centers between the top and bottom of the area, so
-            // the area does not contain any text.
+            // The entire area is between two lines, so it does not contain any text.
             return null;
         }
 
-        int start = getStartOrEndOffsetForHorizontalInterval(
-                startLine, area.left, area.right, segmentIterator, /* getStart= */ true);
+        int start = getStartOrEndOffsetForAreaWithinLine(
+                startLine, area, segmentFinder, inclusionStrategy, /* getStart= */ true);
         // If the area does not contain any text on this line, keep trying subsequent lines until
         // the end line is reached.
         while (start == -1 && startLine < endLine) {
             startLine++;
-            start = getStartOrEndOffsetForHorizontalInterval(
-                    startLine, area.left, area.right, segmentIterator, /* getStart= */ true);
+            start = getStartOrEndOffsetForAreaWithinLine(
+                    startLine, area, segmentFinder, inclusionStrategy, /* getStart= */ true);
         }
         if (start == -1) {
             // All lines were checked, the area does not contain any text.
             return null;
         }
 
-        int end = getStartOrEndOffsetForHorizontalInterval(
-                endLine, area.left, area.right, segmentIterator, /* getStart= */ false);
+        int end = getStartOrEndOffsetForAreaWithinLine(
+                endLine, area, segmentFinder, inclusionStrategy, /* getStart= */ false);
         // If the area does not contain any text on this line, keep trying previous lines until
         // the start line is reached.
         while (end == -1 && startLine < endLine) {
             endLine--;
-            end = getStartOrEndOffsetForHorizontalInterval(
-                    endLine, area.left, area.right, segmentIterator, /* getStart= */ false);
+            end = getStartOrEndOffsetForAreaWithinLine(
+                    endLine, area, segmentFinder, inclusionStrategy, /* getStart= */ false);
         }
         if (end == -1) {
             // All lines were checked, the area does not contain any text.
@@ -1893,33 +1915,39 @@
         }
 
         // If a text segment spans multiple lines or multiple directional runs (e.g. a hyphenated
-        // word), then getStartOrEndOffsetForHorizontalInterval() can return an offset in the middle
-        // of a text segment. Adjust the range to include the rest of any partial text segments. If
+        // word), then getStartOrEndOffsetForAreaWithinLine() can return an offset in the middle of
+        // a text segment. Adjust the range to include the rest of any partial text segments. If
         // start is already the start boundary of a text segment, then this is a no-op.
-        start = segmentIterator.previousStartBoundary(start + 1);
-        end = segmentIterator.nextEndBoundary(end - 1);
+        start = segmentFinder.previousStartBoundary(start + 1);
+        end = segmentFinder.nextEndBoundary(end - 1);
 
         return new int[] {start, end};
     }
 
     /**
-     * Finds the start character offset of the first text segment inside a horizontal interval
-     * within a line, or the end character offset of the last text segment inside the horizontal
-     * interval.
+     * Finds the start character offset of the first text segment within a line inside the specified
+     * rectangle area, or the end character offset of the last text segment inside the area.
      *
      * @param line index of the line to search
-     * @param left left bound of the horizontal interval
-     * @param right right bound of the horizontal interval
-     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
-     *     segment
-     * @param getStart true to find the start of the first text segment inside the horizontal
-     *     interval, false to find the end of the last text segment
-     * @return the start character offset of the first text segment inside the horizontal interval,
-     *     or the end character offset of the last text segment inside the horizontal interval.
+     * @param area area inside which text segments will be found
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *     specified area
+     * @param getStart true to find the start of the first text segment inside the area, false to
+     *     find the end of the last text segment
+     * @return the start character offset of the first text segment inside the area, or the end
+     *     character offset of the last text segment inside the area.
      */
-    private int getStartOrEndOffsetForHorizontalInterval(
-            @IntRange(from = 0) int line, float left, float right,
-            @NonNull SegmentIterator segmentIterator, boolean getStart) {
+    private int getStartOrEndOffsetForAreaWithinLine(
+            @IntRange(from = 0) int line,
+            @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull TextInclusionStrategy inclusionStrategy,
+            boolean getStart) {
+        int lineTop = getLineTop(line);
+        int lineBottom = getLineBottom(line, /* includeLineSpacing= */ false);
+
         int lineStartOffset = getLineStart(line);
         int lineEndOffset = getLineEnd(line);
         if (lineStartOffset == lineEndOffset) {
@@ -1952,14 +1980,16 @@
 
             int result =
                     getStart
-                            ? getStartOffsetForHorizontalIntervalWithinRun(
-                            left, right, lineStartOffset, lineStartPos, horizontalBounds,
-                            runStartOffset, runEndOffset, runLeft, runRight, isRtl,
-                            segmentIterator)
-                            : getEndOffsetForHorizontalIntervalWithinRun(
-                                    left, right, lineStartOffset, lineStartPos, horizontalBounds,
+                            ? getStartOffsetForAreaWithinRun(
+                                    area, lineTop, lineBottom,
+                                    lineStartOffset, lineStartPos, horizontalBounds,
                                     runStartOffset, runEndOffset, runLeft, runRight, isRtl,
-                                    segmentIterator);
+                                    segmentFinder, inclusionStrategy)
+                            : getEndOffsetForAreaWithinRun(
+                                    area, lineTop, lineBottom,
+                                    lineStartOffset, lineStartPos, horizontalBounds,
+                                    runStartOffset, runEndOffset, runLeft, runRight, isRtl,
+                                    segmentFinder, inclusionStrategy);
             if (result >= 0) {
                 return result;
             }
@@ -1970,11 +2000,12 @@
     }
 
     /**
-     * Finds the start character offset of the first text segment inside a horizontal interval
-     * within a directional run.
+     * Finds the start character offset of the first text segment within a directional run inside
+     * the specified rectangle area.
      *
-     * @param left left bound of the horizontal interval
-     * @param right right bound of the horizontal interval
+     * @param area area inside which text segments will be found
+     * @param lineTop top of the line containing this run
+     * @param lineBottom bottom (not including line spacing) of the line containing this run
      * @param lineStartOffset start character offset of the line containing this run
      * @param lineStartPos start position of the line containing this run
      * @param horizontalBounds array containing the signed horizontal bounds of the characters in
@@ -1985,28 +2016,32 @@
      * @param runLeft left bound of the run
      * @param runRight right bound of the run
      * @param isRtl whether the run is right-to-left
-     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
-     *     segment
-     * @return the start character offset of the first text segment inside the horizontal interval
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *     specified area
+     * @return the start character offset of the first text segment inside the area
      */
-    private static int getStartOffsetForHorizontalIntervalWithinRun(
-            float left, float right,
+    private static int getStartOffsetForAreaWithinRun(
+            @NonNull RectF area,
+            int lineTop, int lineBottom,
             @IntRange(from = 0) int lineStartOffset,
             @IntRange(from = 0) int lineStartPos,
             @NonNull float[] horizontalBounds,
             @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset,
             float runLeft, float runRight,
             boolean isRtl,
-            @NonNull SegmentIterator segmentIterator) {
-        if (runRight < left || runLeft > right) {
-            // The run does not overlap the interval.
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull TextInclusionStrategy inclusionStrategy) {
+        if (runRight < area.left || runLeft > area.right) {
+            // The run does not overlap the area.
             return -1;
         }
 
-        // Find the first character in the run whose bounds overlap with the interval.
+        // Find the first character in the run whose bounds overlap with the area.
         // firstCharOffset is an offset index within the line.
         int firstCharOffset;
-        if ((!isRtl && left <= runLeft) || (isRtl && right >= runRight)) {
+        if ((!isRtl && area.left <= runLeft) || (isRtl && area.right >= runRight)) {
             firstCharOffset = runStartOffset;
         } else {
             int low = runStartOffset;
@@ -2016,73 +2051,78 @@
                 guess = (high + low) / 2;
                 // Left edge of the character at guess
                 float pos = lineStartPos + horizontalBounds[2 * guess];
-                if ((!isRtl && pos > left) || (isRtl && pos < right)) {
+                if ((!isRtl && pos > area.left) || (isRtl && pos < area.right)) {
                     high = guess;
                 } else {
                     low = guess;
                 }
             }
-            // The interval bound is between the left edge of the character at low and the left edge
-            // of the character at high. For LTR text, this is within the character at low. For RTL
+            // The area edge is between the left edge of the character at low and the left edge of
+            // the character at high. For LTR text, this is within the character at low. For RTL
             // text, this is within the character at high.
             firstCharOffset = isRtl ? high : low;
         }
 
         // Find the first text segment containing this character (or, if no text segment contains
         // this character, the first text segment after this character). All previous text segments
-        // in this run are to the left (for LTR) of the interval.
-        segmentIterator.setRunLimits(
-                lineStartOffset + runStartOffset, lineStartOffset + runEndOffset);
+        // in this run are to the left (for LTR) of the area.
         int segmentEndOffset =
-                segmentIterator.nextEndBoundaryOrRunEnd(lineStartOffset + firstCharOffset);
-        if (segmentEndOffset == SegmentIterator.DONE) {
+                segmentFinder.nextEndBoundary(lineStartOffset + firstCharOffset);
+        if (segmentEndOffset == SegmentFinder.DONE) {
             // There are no text segments containing or after firstCharOffset, so no text segments
-            // in this run overlap the interval.
+            // in this run overlap the area.
             return -1;
         }
-        int segmentStartOffset = segmentIterator.previousStartBoundaryOrRunStart(segmentEndOffset);
-        float segmentCenter = lineStartPos
-                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
-                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
-        if ((!isRtl && segmentCenter > right) || (isRtl && segmentCenter < left)) {
-            // The entire interval is to the left (for LTR) of the text segment's center. So the
-            // interval does not contain any text segments within this run.
+        int segmentStartOffset = segmentFinder.previousStartBoundary(segmentEndOffset);
+        if (segmentStartOffset >= lineStartOffset + runEndOffset) {
+            // The text segment is after the end of this run, so no text segments in this run
+            // overlap the area.
             return -1;
         }
-        if ((!isRtl && segmentCenter >= left) || (isRtl && segmentCenter <= right)) {
-            // The center is within the interval, so return the start offset of this text segment.
-            return segmentStartOffset;
-        }
+        // If the segment extends outside of this run, only consider the piece of the segment within
+        // this run.
+        segmentStartOffset = Math.max(segmentStartOffset, lineStartOffset + runStartOffset);
+        segmentEndOffset = Math.min(segmentEndOffset, lineStartOffset + runEndOffset);
 
-        // If first text segment's center is not within the interval, try the next text segment.
-        segmentStartOffset = segmentIterator.nextStartBoundaryWithinRunLimits(segmentStartOffset);
-        if (segmentStartOffset == SegmentIterator.DONE
-                || segmentStartOffset == lineStartOffset + runEndOffset) {
-            // No more text segments within this run.
-            return -1;
+        RectF segmentBounds = new RectF(0, lineTop, 0, lineBottom);
+        while (true) {
+            // Start (left for LTR, right for RTL) edge of the character at segmentStartOffset.
+            float segmentStart = lineStartPos + horizontalBounds[
+                    2 * (segmentStartOffset - lineStartOffset) + (isRtl ? 1 : 0)];
+            if ((!isRtl && segmentStart > area.right) || (isRtl && segmentStart < area.left)) {
+                // The entire area is to the left (for LTR) of the text segment. So the area does
+                // not contain any text segments within this run.
+                return -1;
+            }
+            // End (right for LTR, left for RTL) edge of the character at (segmentStartOffset - 1).
+            float segmentEnd = lineStartPos + horizontalBounds[
+                    2 * (segmentEndOffset - lineStartOffset - 1) + (isRtl ? 0 : 1)];
+            segmentBounds.left = isRtl ? segmentEnd : segmentStart;
+            segmentBounds.right = isRtl ? segmentStart : segmentEnd;
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) {
+                return segmentStartOffset;
+            }
+            // Try the next text segment.
+            segmentStartOffset = segmentFinder.nextStartBoundary(segmentStartOffset);
+            if (segmentStartOffset == SegmentFinder.DONE
+                    || segmentStartOffset >= lineStartOffset + runEndOffset) {
+                // No more text segments within this run.
+                return -1;
+            }
+            segmentEndOffset = segmentFinder.nextEndBoundary(segmentStartOffset);
+            // If the segment extends past the end of this run, only consider the piece of the
+            // segment within this run.
+            segmentEndOffset = Math.min(segmentEndOffset, lineStartOffset + runEndOffset);
         }
-        segmentEndOffset = segmentIterator.nextEndBoundaryOrRunEnd(segmentStartOffset);
-        segmentCenter = lineStartPos
-                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
-                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
-        // We already know that segmentCenter >= left (for LTR) since the previous word contains the
-        // left point.
-        if ((!isRtl && segmentCenter <= right) || (isRtl && segmentCenter >= left)) {
-            return segmentStartOffset;
-        }
-
-        // If the second text segment is also not within the interval, then this means that the
-        // interval is between the centers of the first and second text segments, so it does not
-        // contain any text segments on this line.
-        return -1;
     }
 
     /**
-     * Finds the end character offset of the last text segment inside a horizontal interval within a
-     * directional run.
+     * Finds the end character offset of the last text segment within a directional run inside the
+     * specified rectangle area.
      *
-     * @param left left bound of the horizontal interval
-     * @param right right bound of the horizontal interval
+     * @param area area inside which text segments will be found
+     * @param lineTop top of the line containing this run
+     * @param lineBottom bottom (not including line spacing) of the line containing this run
      * @param lineStartOffset start character offset of the line containing this run
      * @param lineStartPos start position of the line containing this run
      * @param horizontalBounds array containing the signed horizontal bounds of the characters in
@@ -2093,28 +2133,32 @@
      * @param runLeft left bound of the run
      * @param runRight right bound of the run
      * @param isRtl whether the run is right-to-left
-     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
-     *     segment
-     * @return the end character offset of the last text segment inside the horizontal interval
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *     specified area
+     * @return the end character offset of the last text segment inside the area
      */
-    private static int getEndOffsetForHorizontalIntervalWithinRun(
-            float left, float right,
+    private static int getEndOffsetForAreaWithinRun(
+            @NonNull RectF area,
+            int lineTop, int lineBottom,
             @IntRange(from = 0) int lineStartOffset,
             @IntRange(from = 0) int lineStartPos,
             @NonNull float[] horizontalBounds,
             @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset,
             float runLeft, float runRight,
             boolean isRtl,
-            @NonNull SegmentIterator segmentIterator) {
-        if (runRight < left || runLeft > right) {
-            // The run does not overlap the interval.
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull TextInclusionStrategy inclusionStrategy) {
+        if (runRight < area.left || runLeft > area.right) {
+            // The run does not overlap the area.
             return -1;
         }
 
-        // Find the last character in the run whose bounds overlap with the interval.
+        // Find the last character in the run whose bounds overlap with the area.
         // firstCharOffset is an offset index within the line.
         int lastCharOffset;
-        if ((!isRtl && right >= runRight) || (isRtl && left <= runLeft)) {
+        if ((!isRtl && area.right >= runRight) || (isRtl && area.left <= runLeft)) {
             lastCharOffset = runEndOffset - 1;
         } else {
             int low = runStartOffset;
@@ -2124,67 +2168,70 @@
                 guess = (high + low) / 2;
                 // Left edge of the character at guess
                 float pos = lineStartPos + horizontalBounds[2 * guess];
-                if ((!isRtl && pos > right) || (isRtl && pos < left)) {
+                if ((!isRtl && pos > area.right) || (isRtl && pos < area.left)) {
                     high = guess;
                 } else {
                     low = guess;
                 }
             }
-            // The interval bound is between the left edge of the character at low and the left edge
-            // of the character at high. For LTR text, this is within the character at low. For RTL
+            // The area edge is between the left edge of the character at low and the left edge of
+            // the character at high. For LTR text, this is within the character at low. For RTL
             // text, this is within the character at high.
             lastCharOffset = isRtl ? high : low;
         }
 
-        // Find the last text segment containing this character (or, if no text segment
-        // contains this character, the first text segment before this character). All
-        // following text segments in this run are to the right (for LTR) of the interval.
-        segmentIterator.setRunLimits(
-                lineStartOffset + runStartOffset, lineStartOffset + runEndOffset);
+        // Find the last text segment containing this character (or, if no text segment contains
+        // this character, the first text segment before this character). All following text
+        // segments in this run are to the right (for LTR) of the area.
         // + 1 to allow segmentStartOffset = lineStartOffset + lastCharOffset
         int segmentStartOffset =
-                segmentIterator.previousStartBoundaryOrRunStart(
-                        lineStartOffset + lastCharOffset + 1);
-        if (segmentStartOffset == SegmentIterator.DONE) {
+                segmentFinder.previousStartBoundary(lineStartOffset + lastCharOffset + 1);
+        if (segmentStartOffset == SegmentFinder.DONE) {
             // There are no text segments containing or before lastCharOffset, so no text segments
-            // in this run overlap the interval.
+            // in this run overlap the area.
             return -1;
         }
-        int segmentEndOffset = segmentIterator.nextEndBoundaryOrRunEnd(segmentStartOffset);
-        float segmentCenter = lineStartPos
-                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
-                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
-        if ((!isRtl && segmentCenter < left) || (isRtl && segmentCenter > right)) {
-            // The entire interval is to the right (for LTR) of the text segment's center. So the
-            // interval does not contain any text segments within this run.
+        int segmentEndOffset = segmentFinder.nextEndBoundary(segmentStartOffset);
+        if (segmentEndOffset <= lineStartOffset + runStartOffset) {
+            // The text segment is before the start of this run, so no text segments in this run
+            // overlap the area.
             return -1;
         }
-        if ((!isRtl && segmentCenter <= right) || (isRtl && segmentCenter >= left)) {
-            // The center is within the interval, so return the end offset of this text segment.
-            return segmentEndOffset;
-        }
+        // If the segment extends outside of this run, only consider the piece of the segment within
+        // this run.
+        segmentStartOffset = Math.max(segmentStartOffset, lineStartOffset + runStartOffset);
+        segmentEndOffset = Math.min(segmentEndOffset, lineStartOffset + runEndOffset);
 
-        // If first text segment's center is not within the interval, try the next text segment.
-        segmentEndOffset = segmentIterator.previousEndBoundaryWithinRunLimits(segmentEndOffset);
-        if (segmentEndOffset == SegmentIterator.DONE
-                || segmentEndOffset == lineStartOffset + runStartOffset) {
-            // No more text segments within this run.
-            return -1;
+        RectF segmentBounds = new RectF(0, lineTop, 0, lineBottom);
+        while (true) {
+            // End (right for LTR, left for RTL) edge of the character at (segmentStartOffset - 1).
+            float segmentEnd = lineStartPos + horizontalBounds[
+                    2 * (segmentEndOffset - lineStartOffset - 1) + (isRtl ? 0 : 1)];
+            if ((!isRtl && segmentEnd < area.left) || (isRtl && segmentEnd > area.right)) {
+                // The entire area is to the right (for LTR) of the text segment. So the
+                // area does not contain any text segments within this run.
+                return -1;
+            }
+            // Start (left for LTR, right for RTL) edge of the character at segmentStartOffset.
+            float segmentStart = lineStartPos + horizontalBounds[
+                    2 * (segmentStartOffset - lineStartOffset) + (isRtl ? 1 : 0)];
+            segmentBounds.left = isRtl ? segmentEnd : segmentStart;
+            segmentBounds.right = isRtl ? segmentStart : segmentEnd;
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) {
+                return segmentEndOffset;
+            }
+            // Try the previous text segment.
+            segmentEndOffset = segmentFinder.previousEndBoundary(segmentEndOffset);
+            if (segmentEndOffset == SegmentFinder.DONE
+                    || segmentEndOffset <= lineStartOffset + runStartOffset) {
+                // No more text segments within this run.
+                return -1;
+            }
+            segmentStartOffset = segmentFinder.previousStartBoundary(segmentEndOffset);
+            // If the segment extends past the start of this run, only consider the piece of the
+            // segment within this run.
+            segmentStartOffset = Math.max(segmentStartOffset, lineStartOffset + runStartOffset);
         }
-        segmentStartOffset = segmentIterator.previousStartBoundaryOrRunStart(segmentEndOffset);
-        segmentCenter = lineStartPos
-                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
-                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
-        // We already know that segmentCenter <= right (for LTR) since the following word
-        // contains the right point.
-        if ((!isRtl && segmentCenter >= left) || (isRtl && segmentCenter <= right)) {
-            return segmentEndOffset;
-        }
-
-        // If the second text segment is also not within the interval, then this means that the
-        // interval is between the centers of the first and second text segments, so it does not
-        // contain any text segments on this line.
-        return -1;
     }
 
     /**
@@ -2229,17 +2276,21 @@
      * Return the vertical position of the bottom of the specified line.
      */
     public final int getLineBottom(int line) {
-        return getLineTop(line + 1);
+        return getLineBottom(line, /* includeLineSpacing= */ true);
     }
 
     /**
-     * Return the vertical position of the bottom of the specified line without the line spacing
-     * added.
+     * Return the vertical position of the bottom of the specified line.
      *
-     * @hide
+     * @param line index of the line
+     * @param includeLineSpacing whether to include the line spacing
      */
-    public final int getLineBottomWithoutSpacing(int line) {
-        return getLineTop(line + 1) - getLineExtra(line);
+    public int getLineBottom(int line, boolean includeLineSpacing) {
+        if (includeLineSpacing) {
+            return getLineTop(line + 1);
+        } else {
+            return getLineTop(line + 1) - getLineExtra(line);
+        }
     }
 
     /**
@@ -2394,7 +2445,7 @@
 
         int line = getLineForOffset(point);
         int top = getLineTop(line);
-        int bottom = getLineBottomWithoutSpacing(line);
+        int bottom = getLineBottom(line, /* includeLineSpacing= */ false);
 
         boolean clamped = shouldClampCursor(line);
         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
@@ -2530,7 +2581,7 @@
         final int endline = getLineForOffset(end);
 
         int top = getLineTop(startline);
-        int bottom = getLineBottomWithoutSpacing(endline);
+        int bottom = getLineBottom(endline, /* includeLineSpacing= */ false);
 
         if (startline == endline) {
             addSelection(startline, start, end, top, bottom, consumer);
@@ -2559,7 +2610,7 @@
             }
 
             top = getLineTop(endline);
-            bottom = getLineBottomWithoutSpacing(endline);
+            bottom = getLineBottom(endline, /* includeLineSpacing= */ false);
 
             addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
 
@@ -3154,4 +3205,22 @@
                 @TextSelectionLayout int textSelectionLayout);
     }
 
+    /**
+     * Strategy for determining whether a text segment is inside a rectangle area.
+     *
+     * @see #getRangeForRect(RectF, SegmentFinder, TextInclusionStrategy)
+     */
+    @FunctionalInterface
+    public interface TextInclusionStrategy {
+        /**
+         * Returns true if this {@link TextInclusionStrategy} considers the segment with bounds
+         * {@code segmentBounds} to be inside {@code area}.
+         *
+         * <p>The segment is a range of text which does not cross line boundaries or directional run
+         * boundaries. The horizontal bounds of the segment are the start bound of the first
+         * character to the end bound of the last character. The vertical bounds match the line
+         * bounds ({@code getLineTop(line)} and {@code getLineBottom(line, false)}).
+         */
+        boolean isSegmentInside(@NonNull RectF segmentBounds, @NonNull RectF area);
+    }
 }
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index 0b51b2d..a6be687 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -1,4 +1,10 @@
 set noparent
 
+halilibo@google.com
+haoyuchang@google.com
+justinghan@google.com
+klippenstein@google.com
+nona@google.com
+seanmcq@google.com
 siyamed@google.com
-nona@google.com
\ No newline at end of file
+soboleva@google.com
diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java
new file mode 100644
index 0000000..d34365d
--- /dev/null
+++ b/core/java/android/text/SegmentFinder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import android.annotation.IntRange;
+import android.graphics.RectF;
+
+/**
+ * Finds text segment boundaries within text. Subclasses can implement different types of text
+ * segments. Grapheme clusters and words are examples of possible text segments. These are
+ * implemented by {@link GraphemeClusterSegmentFinder} and {@link WordSegmentFinder}.
+ *
+ * <p>Text segments may not overlap, so every character belongs to at most one text segment. A
+ * character may belong to no text segments.
+ *
+ * <p>For example, WordSegmentFinder subdivides the text "Hello, World!" into four text segments:
+ * "Hello", ",", "World", "!". The space character does not belong to any text segments.
+ *
+ * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
+ */
+public abstract class SegmentFinder {
+    public static final int DONE = -1;
+
+    /**
+     * Returns the character offset of the previous text segment start boundary before the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int previousStartBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the previous text segment end boundary before the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int previousEndBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the next text segment start boundary after the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int nextStartBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the next text segment end boundary after the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
+}
diff --git a/core/java/android/text/SegmentIterator.java b/core/java/android/text/SegmentIterator.java
deleted file mode 100644
index 00522e5..0000000
--- a/core/java/android/text/SegmentIterator.java
+++ /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 android.text;
-
-import android.annotation.IntRange;
-
-/**
- * Finds text segment boundaries within text. Subclasses can implement different types of text
- * segments. Grapheme clusters and words are examples of possible text segments.
- *
- * <p>Granular units may not overlap, so every character belongs to at most one text segment. A
- * character may belong to no text segments.
- *
- * <p>For example, a word level text segment iterator may subdivide the text "Hello, World!" into
- * four text segments: "Hello", ",", "World", "!". The space character does not belong to any text
- * segments.
- *
- * @hide
- */
-public abstract class SegmentIterator {
-    public static final int DONE = -1;
-
-    private int mRunStartOffset;
-    private int mRunEndOffset;
-
-    /**
-     * Returns the character offset of the previous text segment start boundary before the specified
-     * character offset, or {@code DONE} if there are none.
-     */
-    public abstract int previousStartBoundary(@IntRange(from = 0) int offset);
-
-    /**
-     * Returns the character offset of the previous text segment end boundary before the specified
-     * character offset, or {@code DONE} if there are none.
-     */
-    public abstract int previousEndBoundary(@IntRange(from = 0) int offset);
-
-    /**
-     * Returns the character offset of the next text segment start boundary after the specified
-     * character offset, or {@code DONE} if there are none.
-     */
-    public abstract int nextStartBoundary(@IntRange(from = 0) int offset);
-
-    /**
-     * Returns the character offset of the next text segment end boundary after the specified
-     * character offset, or {@code DONE} if there are none.
-     */
-    public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
-
-    /**
-     * Sets the start and end of a run which can be used to constrain the scope of the iterator's
-     * search.
-     *
-     * @hide
-     */
-    void setRunLimits(
-            @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset) {
-        mRunStartOffset = runStartOffset;
-        mRunEndOffset = runEndOffset;
-    }
-
-    /** @hide */
-    int previousStartBoundaryOrRunStart(@IntRange(from = 0) int offset) {
-        int start = previousStartBoundary(offset);
-        if (start == DONE) {
-            return DONE;
-        }
-        return Math.max(start, mRunStartOffset);
-    }
-
-    /** @hide */
-    int previousEndBoundaryWithinRunLimits(@IntRange(from = 0) int offset) {
-        int end = previousEndBoundary(offset);
-        if (end <= mRunStartOffset) {
-            return DONE;
-        }
-        return end;
-    }
-
-    /** @hide */
-    int nextStartBoundaryWithinRunLimits(@IntRange(from = 0) int offset) {
-        int start = nextStartBoundary(offset);
-        if (start >= mRunEndOffset) {
-            return DONE;
-        }
-        return start;
-    }
-
-    /** @hide */
-    int nextEndBoundaryOrRunEnd(@IntRange(from = 0) int offset) {
-        int end = nextEndBoundary(offset);
-        if (end == DONE) {
-            return DONE;
-        }
-        return Math.min(end, mRunEndOffset);
-    }
-}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 51e3665..596e491 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -93,7 +93,9 @@
     private static final String ELLIPSIS_NORMAL = "\u2026"; // HORIZONTAL ELLIPSIS (…)
     private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // TWO DOT LEADER (‥)
 
-    private static final int LINE_FEED_CODE_POINT = 10;
+    /** @hide */
+    public static final int LINE_FEED_CODE_POINT = 10;
+
     private static final int NBSP_CODE_POINT = 160;
 
     /**
@@ -2335,11 +2337,29 @@
                 || codePoint == LINE_FEED_CODE_POINT;
     }
 
-    private static boolean isWhiteSpace(int codePoint) {
+    /** @hide */
+    public static boolean isWhitespace(int codePoint) {
         return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
     }
 
     /** @hide */
+    public static boolean isWhitespaceExceptNewline(int codePoint) {
+        return isWhitespace(codePoint) && !isNewline(codePoint);
+    }
+
+    /** @hide */
+    public static boolean isPunctuation(int codePoint) {
+        int type = Character.getType(codePoint);
+        return type == Character.CONNECTOR_PUNCTUATION
+                || type == Character.DASH_PUNCTUATION
+                || type == Character.END_PUNCTUATION
+                || type == Character.FINAL_QUOTE_PUNCTUATION
+                || type == Character.INITIAL_QUOTE_PUNCTUATION
+                || type == Character.OTHER_PUNCTUATION
+                || type == Character.START_PUNCTUATION;
+    }
+
+    /** @hide */
     @Nullable
     public static String withoutPrefix(@Nullable String prefix, @Nullable String str) {
         if (prefix == null || str == null) return str;
@@ -2430,7 +2450,7 @@
                 gettingCleaned.removeRange(offset, offset + codePointLen);
             } else if (type == Character.CONTROL && !isNewline) {
                 gettingCleaned.removeRange(offset, offset + codePointLen);
-            } else if (trim && !isWhiteSpace(codePoint)) {
+            } else if (trim && !isWhitespace(codePoint)) {
                 // This is only executed if the code point is not removed
                 if (firstNonWhiteSpace == -1) {
                     firstNonWhiteSpace = offset;
diff --git a/core/java/android/text/WordSegmentIterator.java b/core/java/android/text/WordSegmentFinder.java
similarity index 72%
rename from core/java/android/text/WordSegmentIterator.java
rename to core/java/android/text/WordSegmentFinder.java
index 1115319..bf31e4c 100644
--- a/core/java/android/text/WordSegmentIterator.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -18,21 +18,36 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.icu.text.BreakIterator;
 import android.text.method.WordIterator;
 
+import java.util.Locale;
+
 /**
- * Implementation of {@code SegmentIterator} using words as the text segment. Word boundaries are
- * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
+ * Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
+ * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
  * any text segments.
  *
- * @hide
+ * <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
+ * ",", "World", "!". The space character does not belong to any text segments.
+ *
+ * @see <a href="https://unicode.org/reports/tr29/#Word_Boundaries">Unicode Text Segmentation - Word
+ *     Boundaries</a>
  */
-public class WordSegmentIterator extends SegmentIterator {
+public class WordSegmentFinder extends SegmentFinder {
     private final CharSequence mText;
     private final WordIterator mWordIterator;
 
-    public WordSegmentIterator(@NonNull CharSequence text, @NonNull WordIterator wordIterator) {
+    public WordSegmentFinder(
+            @NonNull CharSequence text, @SuppressLint("UseIcu") @NonNull Locale locale) {
+        mText = text;
+        mWordIterator = new WordIterator(locale);
+        mWordIterator.setCharSequence(text, 0, text.length());
+    }
+
+    /** @hide */
+    public WordSegmentFinder(@NonNull CharSequence text, @NonNull WordIterator wordIterator) {
         mText = text;
         mWordIterator = wordIterator;
     }
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index f427e1b..6d18d2c 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.text.CharSequenceCharacterIterator;
 import android.text.Selection;
+import android.text.TextUtils;
 
 import java.util.Locale;
 
@@ -275,9 +276,9 @@
     }
 
     /**
-     * If <code>offset</code> is within a group of punctuation as defined
-     * by {@link #isPunctuation(int)}, returns the index of the first character
-     * of that group, otherwise returns BreakIterator.DONE.
+     * If <code>offset</code> is within a group of punctuation as defined by {@link
+     * TextUtils#isPunctuation(int)}, returns the index of the first character of that group,
+     * otherwise returns BreakIterator.DONE.
      *
      * @param offset the offset to search from.
      */
@@ -292,9 +293,9 @@
     }
 
     /**
-     * If <code>offset</code> is within a group of punctuation as defined
-     * by {@link #isPunctuation(int)}, returns the index of the last character
-     * of that group plus one, otherwise returns BreakIterator.DONE.
+     * If <code>offset</code> is within a group of punctuation as defined by {@link
+     * TextUtils#isPunctuation(int)}, returns the index of the last character of that group plus
+     * one, otherwise returns BreakIterator.DONE.
      *
      * @param offset the offset to search from.
      */
@@ -309,8 +310,8 @@
     }
 
     /**
-     * Indicates if the provided offset is after a punctuation character
-     * as defined by {@link #isPunctuation(int)}.
+     * Indicates if the provided offset is after a punctuation character as defined by {@link
+     * TextUtils#isPunctuation(int)}.
      *
      * @param offset the offset to check from.
      * @return Whether the offset is after a punctuation character.
@@ -319,14 +320,14 @@
     public boolean isAfterPunctuation(int offset) {
         if (mStart < offset && offset <= mEnd) {
             final int codePoint = Character.codePointBefore(mCharSeq, offset);
-            return isPunctuation(codePoint);
+            return TextUtils.isPunctuation(codePoint);
         }
         return false;
     }
 
     /**
-     * Indicates if the provided offset is at a punctuation character
-     * as defined by {@link #isPunctuation(int)}.
+     * Indicates if the provided offset is at a punctuation character as defined by {@link
+     * TextUtils#isPunctuation(int)}.
      *
      * @param offset the offset to check from.
      * @return Whether the offset is at a punctuation character.
@@ -335,7 +336,7 @@
     public boolean isOnPunctuation(int offset) {
         if (mStart <= offset && offset < mEnd) {
             final int codePoint = Character.codePointAt(mCharSeq, offset);
-            return isPunctuation(codePoint);
+            return TextUtils.isPunctuation(codePoint);
         }
         return false;
     }
@@ -369,17 +370,6 @@
         return !isOnPunctuation(offset) && isAfterPunctuation(offset);
     }
 
-    private static boolean isPunctuation(int cp) {
-        final int type = Character.getType(cp);
-        return (type == Character.CONNECTOR_PUNCTUATION
-                || type == Character.DASH_PUNCTUATION
-                || type == Character.END_PUNCTUATION
-                || type == Character.FINAL_QUOTE_PUNCTUATION
-                || type == Character.INITIAL_QUOTE_PUNCTUATION
-                || type == Character.OTHER_PUNCTUATION
-                || type == Character.START_PUNCTUATION);
-    }
-
     private boolean isAfterLetterOrDigit(int offset) {
         if (mStart < offset && offset <= mEnd) {
             final int codePoint = Character.codePointBefore(mCharSeq, offset);
diff --git a/core/java/android/app/usage/TimeSparseArray.java b/core/java/android/util/TimeSparseArray.java
similarity index 65%
rename from core/java/android/app/usage/TimeSparseArray.java
rename to core/java/android/util/TimeSparseArray.java
index 2bd6b24..6efc683 100644
--- a/core/java/android/app/usage/TimeSparseArray.java
+++ b/core/java/android/util/TimeSparseArray.java
@@ -14,13 +14,11 @@
  * under the License.
  */
 
-package android.app.usage;
-
-import android.util.LongSparseArray;
-import android.util.Slog;
+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}
  */
@@ -29,53 +27,41 @@
 
     private boolean mWtfReported;
 
-    public TimeSparseArray() {
-        super();
-    }
-
     /**
      * 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 index of the matched element, or -1 if no such match exists.
+     * @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) {
-        // This is essentially a binary search, except that if no match is found
-        // the closest index is returned.
         final int size = size();
+        int result = size;
         int lo = 0;
         int hi = size - 1;
-        int mid = -1;
-        long key = -1;
         while (lo <= hi) {
-            mid = lo + ((hi - lo) / 2);
-            key = keyAt(mid);
+            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;
             }
         }
-
-        if (time < key) {
-            return mid;
-        } else if (time > key && lo < size) {
-            return lo;
-        } else {
-            return -1;
-        }
+        return result;
     }
 
     /**
      * {@inheritDoc}
      *
-     * <p> As this container is being used only to keep {@link android.util.AtomicFile files},
-     * there should not be any collisions. Reporting a {@link Slog#wtf(String, String)} in case that
-     * happens, as that will lead to one whole file being dropped.
+     * <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) {
@@ -93,16 +79,13 @@
      * the given time.
      *
      * @param time The timestamp for which to search the array.
-     * @return The index of the matched element, or -1 if no such match exists.
+     * @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 < 0) {
-            // Everything is larger, so we use the last element, or -1 if the list is empty.
-            return size() - 1;
-        }
 
-        if (keyAt(index) == time) {
+        if (index < size() && keyAt(index) == time) {
             return index;
         }
         return index - 1;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 067946e..30dc0c2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -227,9 +227,18 @@
 
     float getCurrentAnimatorScale();
 
-    // For testing
-    @UnsupportedAppUsage(maxTargetSdk = 28)
-    void setInTouchMode(boolean showFocus);
+    // Request to change the touch mode on the display represented by the displayId parameter.
+    //
+    // If com.android.internal.R.bool.config_perDisplayFocusEnabled is false, then it will request
+    // to change the touch mode on all displays (disregarding displayId parameter).
+    void setInTouchMode(boolean inTouch, int displayId);
+
+    // Request to change the touch mode on all displays (disregarding the value
+    // com.android.internal.R.bool.config_perDisplayFocusEnabled).
+    void setInTouchModeOnAllDisplays(boolean inTouch);
+
+    // Returns the touch mode state for the display represented by the displayId parameter.
+    boolean isInTouchMode(int displayId);
 
     // For StrictMode flashing a red border on violations from the UI
     // thread.  The uid/pid is implicit from the Binder call, and the Window
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index afcec66..0052e82 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -151,11 +151,6 @@
             int seqId);
 
     @UnsupportedAppUsage
-    oneway void setInTouchMode(boolean showFocus);
-    @UnsupportedAppUsage
-    boolean getInTouchMode();
-
-    @UnsupportedAppUsage
     boolean performHapticFeedback(int effectId, boolean always);
 
     /**
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 42c37fd..9fd8ecb 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -92,9 +92,6 @@
     private SensorManager mSensorManager;
 
     @GuardedBy("mMotionRanges")
-    private BatteryState mBatteryState;
-
-    @GuardedBy("mMotionRanges")
     private LightsManager mLightsManager;
 
     /**
@@ -1058,10 +1055,7 @@
      */
     @NonNull
     public BatteryState getBatteryState() {
-        if (mBatteryState == null) {
-            mBatteryState = InputManager.getInstance().getInputDeviceBatteryState(mId, mHasBattery);
-        }
-        return mBatteryState;
+        return InputManager.getInstance().getInputDeviceBatteryState(mId, mHasBattery);
     }
 
     /**
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5832527..c8c941a 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -22,6 +22,7 @@
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -148,7 +149,7 @@
         // During drag-move and drag-resizing, the caption insets position may not get updated
         // before the app frame get updated. To layout the app content correctly during drag events,
         // we always return the insets with the corresponding height covering the top.
-        if (getType() == ITYPE_CAPTION_BAR) {
+        if (!CAPTION_ON_SHELL && getType() == ITYPE_CAPTION_BAR) {
             return Insets.of(0, frame.height(), 0, 0);
         }
         // Checks for whether there is shared edge with insets for 0-width/height window.
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ea00125..b5dff5e 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1273,8 +1273,8 @@
     public static final int AXIS_GENERIC_16 = 47;
 
     // NOTE: If you add a new axis here you must also add it to:
-    //  native/include/android/input.h
-    //  frameworks/base/include/ui/KeycodeLabels.h
+    //  frameworks/native/include/android/input.h
+    //  frameworks/native/libs/input/InputEventLabels.cpp
 
     // Symbolic names of all axes.
     private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
@@ -1457,10 +1457,20 @@
      */
     public static final int CLASSIFICATION_DEEP_PRESS = 2;
 
+    /**
+     * Classification constant: touchpad scroll.
+     *
+     * The current event stream represents the user scrolling with two fingers on a touchpad.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3;
+
     /** @hide */
     @Retention(SOURCE)
     @IntDef(prefix = { "CLASSIFICATION" }, value = {
-            CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS})
+            CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS,
+            CLASSIFICATION_TWO_FINGER_SWIPE})
     public @interface Classification {};
 
     /**
@@ -3862,9 +3872,11 @@
                 return "AMBIGUOUS_GESTURE";
             case CLASSIFICATION_DEEP_PRESS:
                 return "DEEP_PRESS";
+            case CLASSIFICATION_TWO_FINGER_SWIPE:
+                return "TWO_FINGER_SWIPE";
 
         }
-        return "NONE";
+        return "UNKNOWN";
     }
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 9322bf6..3ffb781 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -57,7 +57,6 @@
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.graphics.common.DisplayDecorationSupport;
-import android.media.projection.MediaProjectionGlobal;
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSync;
 import android.os.Build;
@@ -225,8 +224,6 @@
     private static native void nativeSetDimmingEnabled(long transactionObj, long nativeObject,
             boolean dimmingEnabled);
 
-    private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
-
     private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
             InputWindowHandle handle);
 
@@ -2017,8 +2014,8 @@
         }
 
         // We don't have a size yet so pass in 1 for width and height since 0 is invalid
-        VirtualDisplay vd = MediaProjectionGlobal.getInstance().createVirtualDisplay(name,
-                1 /* width */, 1 /* height */, INVALID_DISPLAY, null /* Surface */);
+        VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
+                INVALID_DISPLAY, null /* Surface */);
         return vd == null ? null : vd.getToken().asBinder();
     }
 
@@ -2038,18 +2035,6 @@
     }
 
     /**
-     * Overrides HDR modes for a display device.
-     *
-     * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
-     * Exception.
-     * @hide
-     */
-    @TestApi
-    public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
-        nativeOverrideHdrTypes(displayToken, modes);
-    }
-
-    /**
      * @hide
      */
     public static long[] getPhysicalDisplayIds() {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index c97eb73..d77e882 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -196,8 +196,6 @@
      */
     public static boolean sRendererEnabled = true;
 
-    public static boolean sTrimForeground = false;
-
     /**
      * Controls whether or not the renderer should aggressively trim
      * memory. Note that this must not be set for any process that uses
@@ -205,9 +203,10 @@
      * that do not go into the background.
      */
     public static void enableForegroundTrimming() {
-        sTrimForeground = true;
+        // TODO: Remove
     }
 
+
     /**
      * Initialize HWUI for being in a system process like system_server
      * Should not be called in non-system processes
@@ -218,9 +217,8 @@
         // process.
         if (!ActivityManager.isHighEndGfx()) {
             sRendererEnabled = false;
-        } else {
-            enableForegroundTrimming();
         }
+        setIsSystemOrPersistent();
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 894ce63..bc665cf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8303,7 +8303,14 @@
                     // We have not been laid out yet, hence cannot evaluate
                     // whether this view is visible to the user, we will do
                     // the evaluation once layout is complete.
-                    if (!isLaidOut()) {
+                    // Sometimes, views are already laid out, but it's still
+                    // not visible to the user, we also do the evaluation once
+                    // the view is visible. ex: There is a fade-in animation
+                    // for the activity, the view will be laid out when the
+                    // animation beginning. On the time, the view is not visible
+                    // to the user. And then as the animation progresses, the view
+                    // becomes visible to the user.
+                    if (!isLaidOut() || !isVisibleToUser()) {
                         mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
                     } else if (isVisibleToUser()) {
                         if (isFocused()) {
@@ -15896,19 +15903,22 @@
     }
 
     /**
-     * Returns whether the device is currently in touch mode.  Touch mode is entered
+     * Returns whether the device is currently in touch mode. Touch mode is entered
      * once the user begins interacting with the device by touch, and affects various
      * things like whether focus is always visible to the user.
      *
+     * If this view has no {@link ViewRootImpl} or {@link Display} attached, then it will return
+     * the default touch mode value defined in
+     * {@code com.android.internal.R.bool.config_defaultInTouchMode}.
+     *
      * @return Whether the device is in touch mode.
      */
     @ViewDebug.ExportedProperty
     public boolean isInTouchMode() {
         if (mAttachInfo != null) {
             return mAttachInfo.mInTouchMode;
-        } else {
-            return ViewRootImpl.isInTouchMode();
         }
+        return mResources.getBoolean(com.android.internal.R.bool.config_defaultInTouchMode);
     }
 
     /**
@@ -24649,7 +24659,7 @@
         int viewStateIndex = 0;
         if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
         if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
-        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+        if (isFocused() && hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
         if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
         if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
         if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6f775d..6c61d4bd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1055,19 +1055,11 @@
         mProfile = true;
     }
 
-    /**
-     * Indicates whether we are in touch mode. Calling this method triggers an IPC
-     * call and should be avoided whenever possible.
-     *
-     * @return True, if the device is in touch mode, false otherwise.
-     *
-     * @hide
-     */
-    static boolean isInTouchMode() {
-        IWindowSession windowSession = WindowManagerGlobal.peekWindowSession();
-        if (windowSession != null) {
+    private boolean isInTouchMode() {
+        IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+        if (windowManager != null) {
             try {
-                return windowSession.getInTouchMode();
+                return windowManager.isInTouchMode(getDisplayId());
             } catch (RemoteException e) {
             }
         }
@@ -1106,6 +1098,10 @@
                 mInputQueueCallback.onInputQueueCreated(mInputQueue);
             }
         }
+
+        // Update the last resource config in case the resource configuration was changed while
+        // activity relaunched.
+        mLastConfigurationFromResources.setTo(getConfiguration());
     }
 
     private Configuration getConfiguration() {
@@ -1766,9 +1762,6 @@
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
-            if (!mAppVisible) {
-                WindowManagerGlobal.trimForeground();
-            }
             AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
         }
     }
@@ -5813,7 +5806,8 @@
 
         // tell the window manager
         try {
-            mWindowSession.setInTouchMode(inTouchMode);
+            IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+            windowManager.setInTouchMode(inTouchMode, getDisplayId());
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -8550,6 +8544,10 @@
         if (mLocalSyncState != LOCAL_SYNC_NONE) {
             writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
         }
+        writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+                + mLastReportedMergedConfiguration);
+        writer.println(innerPrefix + "mLastConfigurationFromResources="
+                + mLastConfigurationFromResources);
         writer.println(innerPrefix + "mIsAmbientMode="  + mIsAmbientMode);
         writer.println(innerPrefix + "mUnbufferedInputSource="
                 + Integer.toHexString(mUnbufferedInputSource));
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d377565..4a9dc5b 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -19,9 +19,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
@@ -524,9 +522,6 @@
             }
             allViewsRemoved = mRoots.isEmpty();
         }
-        if (ThreadedRenderer.sTrimForeground) {
-            doTrimForeground();
-        }
 
         // If we don't have any views anymore in our process, we no longer need the
         // InsetsAnimationThread to save some resources.
@@ -543,65 +538,9 @@
         return index;
     }
 
-    public static boolean shouldDestroyEglContext(int trimLevel) {
-        // On low-end gfx devices we trim when memory is moderate;
-        // on high-end devices we do this when low.
-        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
-            return true;
-        }
-        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
-                && !ActivityManager.isHighEndGfx()) {
-            return true;
-        }
-        return false;
-    }
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public void trimMemory(int level) {
-
-        if (shouldDestroyEglContext(level)) {
-            // Destroy all hardware surfaces and resources associated to
-            // known windows
-            synchronized (mLock) {
-                for (int i = mRoots.size() - 1; i >= 0; --i) {
-                    mRoots.get(i).destroyHardwareResources();
-                }
-            }
-            // Force a full memory flush
-            level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
-        }
-
         ThreadedRenderer.trimMemory(level);
-
-        if (ThreadedRenderer.sTrimForeground) {
-            doTrimForeground();
-        }
-    }
-
-    public static void trimForeground() {
-        if (ThreadedRenderer.sTrimForeground) {
-            WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
-            wm.doTrimForeground();
-        }
-    }
-
-    private void doTrimForeground() {
-        boolean hasVisibleWindows = false;
-        synchronized (mLock) {
-            for (int i = mRoots.size() - 1; i >= 0; --i) {
-                final ViewRootImpl root = mRoots.get(i);
-                if (root.mView != null && root.getHostVisibility() == View.VISIBLE
-                        && root.mAttachInfo.mThreadedRenderer != null) {
-                    hasVisibleWindows = true;
-                } else {
-                    root.destroyHardwareResources();
-                }
-            }
-        }
-        if (!hasVisibleWindows) {
-            ThreadedRenderer.trimMemory(
-                    ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
-        }
     }
 
     public void dumpGfxInfo(FileDescriptor fd, String[] args) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 1ec17d0..fbf7456 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -189,7 +189,8 @@
                         WindowManagerGlobal.ADD_FLAG_USE_BLAST;
 
         // Include whether the window is in touch mode.
-        return isInTouchMode() ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE : res;
+        return isInTouchModeInternal(displayId) ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE
+                : res;
     }
 
     /**
@@ -244,9 +245,9 @@
         return !PixelFormat.formatHasAlpha(attrs.format);
     }
 
-    private boolean isInTouchMode() {
+    private boolean isInTouchModeInternal(int displayId) {
         try {
-            return WindowManagerGlobal.getWindowSession().getInTouchMode();
+            return WindowManagerGlobal.getWindowManagerService().isInTouchMode(displayId);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to check if the window is in touch mode", e);
         }
@@ -399,15 +400,6 @@
     }
 
     @Override
-    public void setInTouchMode(boolean showFocus) {
-    }
-
-    @Override
-    public boolean getInTouchMode() {
-        return false;
-    }
-
-    @Override
     public boolean performHapticFeedback(int effectId, boolean always) {
         return false;
     }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f2c8355..f86f51fc 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.TextView;
 
 import com.android.internal.util.BitUtils;
 
@@ -688,15 +687,27 @@
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
-     * It means the content is invalid or associated with an error.
-     * For example, text that sets an error message, such as when input isn't in a valid format,
-     * should send this event and use {@link AccessibilityNodeInfo#setError} to
-     * provide more context.
+     * The source node changed its content validity returned by
+     * {@link AccessibilityNodeInfo#isContentInvalid}.
+     * The view changing content validity should call
+     * {@link AccessibilityNodeInfo#setContentInvalid} and then send this event.
      *
-     * @see AccessibilityNodeInfo#setError
-     * @see TextView#setError
+     * @see AccessibilityNodeInfo#isContentInvalid
+     * @see AccessibilityNodeInfo#setContentInvalid
      */
-    public static final int CONTENT_CHANGE_TYPE_INVALID = 0x0000400;
+    public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 0x0000400;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The source node changed its erroneous content's error message returned by
+     * {@link AccessibilityNodeInfo#getError}.
+     * The view changing erroneous content's error message should call
+     * {@link AccessibilityNodeInfo#setError} and then send this event.
+     *
+     * @see AccessibilityNodeInfo#getError
+     * @see AccessibilityNodeInfo#setError
+     */
+    public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
 
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
     public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
@@ -823,7 +834,8 @@
                 CONTENT_CHANGE_TYPE_DRAG_STARTED,
                 CONTENT_CHANGE_TYPE_DRAG_DROPPED,
                 CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
-                CONTENT_CHANGE_TYPE_INVALID,
+                CONTENT_CHANGE_TYPE_CONTENT_INVALID,
+                CONTENT_CHANGE_TYPE_ERROR,
             })
     public @interface ContentChangeTypes {}
 
@@ -1090,7 +1102,9 @@
             case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
             case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
             case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
-            case CONTENT_CHANGE_TYPE_INVALID: return "CONTENT_CHANGE_TYPE_INVALID";
+            case CONTENT_CHANGE_TYPE_CONTENT_INVALID:
+                return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
+            case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
             default: return Integer.toHexString(type);
         }
     }
diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS
index b1d3967..73d1341 100644
--- a/core/java/android/view/accessibility/OWNERS
+++ b/core/java/android/view/accessibility/OWNERS
@@ -10,3 +10,7 @@
 jjaggi@google.com
 pweaver@google.com
 ryanlwlin@google.com
+danielnorman@google.com
+sallyyuen@google.com
+aarmaly@google.com
+fuego@google.com
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 90384b5..c32ca9e 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -528,7 +528,12 @@
     @Override
     @UiThread
     void flush(@FlushReason int reason) {
-        if (mEvents == null) return;
+        if (mEvents == null || mEvents.size() == 0) {
+            if (sVerbose) {
+                Log.v(TAG, "Don't flush for empty event buffer.");
+            }
+            return;
+        }
 
         if (mDisabled.get()) {
             Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a72f0d5..537822e 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -237,7 +237,7 @@
      */
     @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
-        if (DEBUG) Log.v(TAG, "commitText " + text);
+        if (DEBUG) Log.v(TAG, "commitText(" + text + ", " + newCursorPosition + ")");
         replaceText(text, newCursorPosition, false);
         sendCurrentText();
         return true;
@@ -260,7 +260,7 @@
      */
     @Override
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
-        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + " / " + afterLength);
+        if (DEBUG) Log.v(TAG, "deleteSurroundingText(" + beforeLength + ", " + afterLength + ")");
         final Editable content = getEditable();
         if (content == null) return false;
 
@@ -747,13 +747,14 @@
      */
     @Override
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
-        if (DEBUG) Log.v(TAG, "setComposingText " + text);
+        if (DEBUG) Log.v(TAG, "setComposingText(" + text + ", " + newCursorPosition + ")");
         replaceText(text, newCursorPosition, true);
         return true;
     }
 
     @Override
     public boolean setComposingRegion(int start, int end) {
+        if (DEBUG) Log.v(TAG, "setComposingRegion(" + start + ", " + end + ")");
         final Editable content = getEditable();
         if (content != null) {
             beginBatchEdit();
@@ -797,7 +798,7 @@
     /** The default implementation changes the selection position in the current editable text. */
     @Override
     public boolean setSelection(int start, int end) {
-        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
+        if (DEBUG) Log.v(TAG, "setSelection(" + start + ", " + end + ")");
         final Editable content = getEditable();
         if (content == null) return false;
         int len = content.length();
@@ -897,8 +898,43 @@
         }
     }
 
-    private void replaceText(CharSequence text, int newCursorPosition,
-            boolean composing) {
+    @Override
+    public boolean replaceText(
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull CharSequence text,
+            int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        Preconditions.checkArgumentNonnegative(start);
+        Preconditions.checkArgumentNonnegative(end);
+
+        if (DEBUG) {
+            Log.v(
+                    TAG,
+                    "replaceText " + start + ", " + end + ", " + text + ", " + newCursorPosition);
+        }
+
+        final Editable content = getEditable();
+        if (content == null) {
+            return false;
+        }
+        beginBatchEdit();
+        removeComposingSpans(content);
+
+        int len = content.length();
+        start = Math.min(start, len);
+        end = Math.min(end, len);
+        if (end < start) {
+            int tmp = start;
+            start = end;
+            end = tmp;
+        }
+        replaceTextInternal(start, end, text, newCursorPosition, /*composing=*/ false);
+        endBatchEdit();
+        return true;
+    }
+
+    private void replaceText(CharSequence text, int newCursorPosition, boolean composing) {
         final Editable content = getEditable();
         if (content == null) {
             return;
@@ -931,6 +967,16 @@
                 b = tmp;
             }
         }
+        replaceTextInternal(a, b, text, newCursorPosition, composing);
+        endBatchEdit();
+    }
+
+    private void replaceTextInternal(
+            int a, int b, CharSequence text, int newCursorPosition, boolean composing) {
+        final Editable content = getEditable();
+        if (content == null) {
+            return;
+        }
 
         if (composing) {
             Spannable sp = null;
@@ -950,11 +996,22 @@
             setComposingSpans(sp);
         }
 
-        if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
-                + text + "\", composing=" + composing
-                + ", type=" + text.getClass().getCanonicalName());
-
         if (DEBUG) {
+            Log.v(
+                    TAG,
+                    "Replacing from "
+                            + a
+                            + " to "
+                            + b
+                            + " with \""
+                            + text
+                            + "\", composing="
+                            + composing
+                            + ", newCursorPosition="
+                            + newCursorPosition
+                            + ", type="
+                            + text.getClass().getCanonicalName());
+
             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
             lp.println("Current text:");
             TextUtils.dumpSpans(content, lp, "  ");
@@ -962,10 +1019,10 @@
             TextUtils.dumpSpans(text, lp, "  ");
         }
 
-        // Position the cursor appropriately, so that after replacing the
-        // desired range of text it will be located in the correct spot.
-        // This allows us to deal with filters performing edits on the text
-        // we are providing here.
+        // Position the cursor appropriately, so that after replacing the desired range of text it
+        // will be located in the correct spot.
+        // This allows us to deal with filters performing edits on the text we are providing here.
+        int requestedNewCursorPosition = newCursorPosition;
         if (newCursorPosition > 0) {
             newCursorPosition += b - 1;
         } else {
@@ -974,16 +1031,20 @@
         if (newCursorPosition < 0) newCursorPosition = 0;
         if (newCursorPosition > content.length()) newCursorPosition = content.length();
         Selection.setSelection(content, newCursorPosition);
-
         content.replace(a, b, text);
 
+        // Replace (or insert) to the cursor (a==b==newCursorPosition) will position the cursor to
+        // the end of the new replaced/inserted text, we need to re-position the cursor to the start
+        // according the API definition: "if <= 0, this is relative to the start of the text".
+        if (requestedNewCursorPosition == 0 && a == b) {
+            Selection.setSelection(content, newCursorPosition);
+        }
+
         if (DEBUG) {
             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
             lp.println("Final text:");
             TextUtils.dumpSpans(content, lp, "  ");
         }
-
-        endBatchEdit();
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
index 429b0b8..a8e1d75 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -90,10 +90,10 @@
     @AnyThread
     @NonNull
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable String imiId,
-            boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
         try {
             return mTarget.getEnabledInputMethodSubtypeList(imiId,
-                    allowsImplicitlySelectedSubtypes, userId);
+                    allowsImplicitlyEnabledSubtypes, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -202,6 +202,16 @@
     }
 
     @AnyThread
+    void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
+            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
+        try {
+            mTarget.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
     int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
         try {
             return mTarget.getInputMethodWindowVisibleHeight(client);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 2f834c9..7d268a9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1329,4 +1329,44 @@
         // existing APIs.
         return null;
     }
+
+    /**
+     * Replace the specific range in the editor with suggested text.
+     *
+     * <p>This method finishes whatever composing text is currently active and leaves the text
+     * as-it, replaces the specific range of text with the passed CharSequence, and then moves the
+     * cursor according to {@code newCursorPosition}. This behaves like calling {@link
+     * #finishComposingText()}, {@link #setSelection(int, int) setSelection(start, end)}, and then
+     * {@link #commitText(CharSequence, int, TextAttribute) commitText(text, newCursorPosition,
+     * textAttribute)}.
+     *
+     * <p>Similar to {@link #setSelection(int, int)}, the order of start and end is not important.
+     * In effect, the region from start to end and the region from end to start is the same. Editor
+     * authors, be ready to accept a start that is greater than end.
+     *
+     * @param start the character index where the replacement should start.
+     * @param end the character index where the replacement should end.
+     * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
+     *     the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
+     *     of 1 will always advance you to the position after the full text being inserted. Note
+     *     that this means you can't position the cursor within the text.
+     * @param text the text to replace. This may include styles.
+     * @param textAttribute The extra information about the text. This value may be null.
+     */
+    default boolean replaceText(
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull CharSequence text,
+            int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        Preconditions.checkArgumentNonnegative(start);
+        Preconditions.checkArgumentNonnegative(end);
+
+        beginBatchEdit();
+        finishComposingText();
+        setSelection(start, end);
+        commitText(text, newCursorPosition, textAttribute);
+        endBatchEdit();
+        return true;
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7794b7c..c1fe686 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -107,7 +107,6 @@
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
-import com.android.internal.inputmethod.RemoteInputConnectionImpl;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -550,7 +549,10 @@
      */
     @Deprecated
     @GuardedBy("mH")
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 236937383,
+            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            publicAlternatives = "Apps should not change behavior based on the currently connected"
+                    + " IME. If absolutely needed, use {@link InputMethodInfo#getId()} instead.")
     String mCurId;
 
     /**
@@ -561,7 +563,9 @@
     @Deprecated
     @GuardedBy("mH")
     @Nullable
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 236937383,
+            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+            publicAlternatives = "Use methods on {@link InputMethodManager} instead.")
     IInputMethodSession mCurMethod;
 
     /**
@@ -719,14 +723,8 @@
             ImeTracing.getInstance().triggerClientDump(
                     "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this,
                     null /* icProto */);
-            synchronized (mH) {
-                mCurrentEditorInfo = null;
-                mCompletions = null;
-                mServedConnecting = true;
-            }
-            return startInputInner(startInputReason,
-                    focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
-                    softInputMode, windowFlags);
+            return startInputOnWindowFocusGainInternal(startInputReason, focusedView,
+                    startInputFlags, softInputMode, windowFlags);
         }
 
         /**
@@ -801,7 +799,7 @@
                 // should be done in conjunction with telling the system service
                 // about the window gaining focus, to help make the transition
                 // smooth.
-                if (startInput(StartInputReason.WINDOW_FOCUS_GAIN,
+                if (startInputOnWindowFocusGainInternal(StartInputReason.WINDOW_FOCUS_GAIN,
                         focusedView, startInputFlags, softInputMode, windowFlags)) {
                     return;
                 }
@@ -925,6 +923,19 @@
         }
     }
 
+    private boolean startInputOnWindowFocusGainInternal(@StartInputReason int startInputReason,
+            View focusedView, @StartInputFlags int startInputFlags,
+            @SoftInputModeFlags int softInputMode, int windowFlags) {
+        synchronized (mH) {
+            mCurrentEditorInfo = null;
+            mCompletions = null;
+            mServedConnecting = true;
+        }
+        return startInputInner(startInputReason,
+                focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
+                softInputMode, windowFlags);
+    }
+
     @GuardedBy("mH")
     private View getServedViewLocked() {
         return mCurRootView != null ? mCurRootView.getImeFocusController().getServedViewLocked()
@@ -1137,7 +1148,7 @@
                                     .checkFocus(mRestartOnNextWindowFocus, false)) {
                                 final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
                                         : StartInputReason.DEACTIVATED_BY_IMMS;
-                                mDelegate.startInput(reason, null, 0, 0, 0);
+                                startInputOnWindowFocusGainInternal(reason, null, 0, 0, 0);
                             }
                         }
                     }
@@ -1578,16 +1589,16 @@
      *
      * @param imi The {@link InputMethodInfo} whose subtypes list will be returned. If {@code null},
      * returns enabled subtypes for the currently selected {@link InputMethodInfo}.
-     * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
-     * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+     * @param allowsImplicitlyEnabledSubtypes A boolean flag to allow to return the implicitly
+     * enabled subtypes. If an input method info doesn't have enabled subtypes, the framework
      * will implicitly enable subtypes according to the current system language.
      */
     @NonNull
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable InputMethodInfo imi,
-            boolean allowsImplicitlySelectedSubtypes) {
+            boolean allowsImplicitlyEnabledSubtypes) {
         return mServiceInvoker.getEnabledInputMethodSubtypeList(
                 imi == null ? null : imi.getId(),
-                allowsImplicitlySelectedSubtypes,
+                allowsImplicitlyEnabledSubtypes,
                 UserHandle.myUserId());
     }
 
@@ -2358,7 +2369,7 @@
             // The view is running on a different thread than our own, so
             // we need to reschedule our work for over there.
             if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
-            vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0));
+            vh.post(() -> startInputOnWindowFocusGainInternal(startInputReason, null, 0, 0, 0));
             return false;
         }
 
@@ -3633,6 +3644,56 @@
     }
 
     /**
+     * Updates the list of explicitly enabled {@link InputMethodSubtype} for a given IME owned by
+     * the calling process.
+     *
+     * <p>By default each IME has no explicitly enabled {@link InputMethodSubtype}.  In this state
+     * the system will decide what {@link InputMethodSubtype} should be enabled by using information
+     * available at runtime as per-user language settings.  Users can, however, manually pick up one
+     * or more {@link InputMethodSubtype} to be enabled on an Activity shown by
+     * {@link #showInputMethodAndSubtypeEnabler(String)}. Such a manual change is stored in
+     * {@link Settings.Secure#ENABLED_INPUT_METHODS} so that the change can persist across reboots.
+     * {@link Settings.Secure#ENABLED_INPUT_METHODS} stores {@link InputMethodSubtype#hashCode()} as
+     * the identifier of {@link InputMethodSubtype} for historical reasons.</p>
+     *
+     * <p>This API provides a safe and managed way for IME developers to modify what
+     * {@link InputMethodSubtype} are referenced in {@link Settings.Secure#ENABLED_INPUT_METHODS}
+     * for their own IME.  One use case is when IME developers want to use their own Activity for
+     * users to pick up {@link InputMethodSubtype}. Another use case is for IME developers to fix up
+     * any stale and/or invalid value stored in {@link Settings.Secure#ENABLED_INPUT_METHODS}
+     * without bothering users. Passing an empty {@code subtypeHashCodes} is guaranteed to reset
+     * the state to default.</p>
+     *
+     * <h3>To control the return value of {@link InputMethodSubtype#hashCode()}</h3>
+     * <p>{@link android.R.attr#subtypeId} and {@link
+     * android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder#setSubtypeId(int)} are
+     * available for IME developers to control the return value of
+     * {@link InputMethodSubtype#hashCode()}. Beware that {@code -1} is not a valid value of
+     * {@link InputMethodSubtype#hashCode()} for historical reasons.</p>
+     *
+     * <h3>Note for Direct Boot support</h3>
+     * <p>While IME developers can call this API even before
+     * {@link android.os.UserManager#isUserUnlocked()} becomes {@code true}, such a change is
+     * volatile thus remains effective only until {@link android.os.UserManager#isUserUnlocked()}
+     * becomes {@code true} or the device is rebooted. To make the change persistent IME developers
+     * need to call this API again after receiving {@link Intent#ACTION_USER_UNLOCKED}.</p>
+     *
+     * @param imiId IME ID. The specified IME and the calling process need to belong to the same
+     *              package.  Otherwise {@link SecurityException} will be thrown.
+     * @param subtypeHashCodes An arrays of {@link InputMethodSubtype#hashCode()} to be explicitly
+     *                         enabled. Entries that are found in the specified IME will be silently
+     *                         ignored. Pass an empty array to reset the state to default.
+     * @throws NullPointerException if {@code subtypeHashCodes} is {@code null}.
+     * @throws SecurityException if the specified IME and the calling process do not belong to the
+     *                           same package.
+     */
+    public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imiId,
+            @NonNull int[] subtypeHashCodes) {
+        mServiceInvoker.setExplicitlyEnabledInputMethodSubtypes(imiId, subtypeHashCodes,
+                UserHandle.myUserId());
+    }
+
+    /**
      * Returns the last used {@link InputMethodSubtype} in system history.
      *
      * @return the last {@link InputMethodSubtype}, {@code null} if last IME have no subtype.
diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java
index 8b8359e..9f03289 100644
--- a/core/java/android/view/inputmethod/InsertGesture.java
+++ b/core/java/android/view/inputmethod/InsertGesture.java
@@ -53,7 +53,7 @@
     }
 
     /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/
-    @Nullable
+    @NonNull
     public String getTextToInsert() {
         return mTextToInsert;
     }
@@ -62,7 +62,7 @@
      * Returns the insertion point {@link PointF} (in screen coordinates) where
      * {@link #getTextToInsert()} will be inserted.
      */
-    @Nullable
+    @NonNull
     public PointF getInsertionPoint() {
         return mPoint;
     }
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
similarity index 97%
rename from core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
rename to core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 713e913..9376878 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.inputmethod;
+package android.view.inputmethod;
 
 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetCursorCapsModeProto;
 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetExtractedTextProto;
@@ -38,26 +38,13 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewRootImpl;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.DeleteGesture;
-import android.view.inputmethod.DeleteRangeGesture;
-import android.view.inputmethod.DumpableInputConnection;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.HandwritingGesture;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InsertGesture;
-import android.view.inputmethod.JoinOrSplitGesture;
-import android.view.inputmethod.RemoveSpaceGesture;
-import android.view.inputmethod.SelectGesture;
-import android.view.inputmethod.SelectRangeGesture;
-import android.view.inputmethod.TextAttribute;
-import android.view.inputmethod.TextSnapshot;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.InputConnectionCommandHeader;
 
 import java.lang.annotation.Retention;
 import java.lang.ref.WeakReference;
@@ -82,7 +69,7 @@
  * (editor app) process, and forwards them to {@link InputConnection} that the IME client provided,
  * on the {@link Looper} associated to the {@link InputConnection}.</p>
  */
-public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
+final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
     private static final String TAG = "RemoteInputConnectionImpl";
     private static final boolean DEBUG = false;
 
@@ -189,7 +176,7 @@
     private final AtomicBoolean mHasPendingImmediateCursorAnchorInfoUpdate =
             new AtomicBoolean(false);
 
-    public RemoteInputConnectionImpl(@NonNull Looper looper,
+    RemoteInputConnectionImpl(@NonNull Looper looper,
             @NonNull InputConnection inputConnection,
             @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
         mInputConnection = inputConnection;
@@ -1185,6 +1172,30 @@
         });
     }
 
+    @Dispatching(cancellable = true)
+    @Override
+    public void replaceText(
+            InputConnectionCommandHeader header,
+            int start,
+            int end,
+            @NonNull CharSequence text,
+            int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        dispatchWithTracing(
+                "replaceText",
+                () -> {
+                    if (header.mSessionId != mCurrentSessionId.get()) {
+                        return; // cancelled
+                    }
+                    InputConnection ic = getInputConnection();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "replaceText on inactive InputConnection");
+                        return;
+                    }
+                    ic.replaceText(start, end, text, newCursorPosition, textAttribute);
+                });
+    }
+
     private final IRemoteAccessibilityInputConnection mAccessibilityInputConnection =
             new IRemoteAccessibilityInputConnection.Stub() {
         @Dispatching(cancellable = true)
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 6bf2474..514df59 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -175,10 +175,7 @@
      */
     public void onActivityDestroyed() {
         synchronized (mLock) {
-            if (DEBUG) {
-                Log.i(TAG,
-                        "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
-            }
+            Log.i(TAG, "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
             if (mCurrentState != STATE_UI_TRANSLATION_FINISHED) {
                 notifyTranslationFinished(/* activityDestroyed= */ true);
             }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 424b8ae..8f590f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -572,8 +572,8 @@
 
         final Layout layout = mTextView.getLayout();
         final int line = layout.getLineForOffset(mTextView.getSelectionStart());
-        final int sourceHeight =
-            layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+        final int sourceHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                - layout.getLineTop(line);
         final int height = (int)(sourceHeight * zoom);
         final int width = (int)(aspectRatio * Math.max(sourceHeight, mMinLineHeightForMagnifier));
 
@@ -2340,7 +2340,7 @@
         final int offset = mTextView.getSelectionStart();
         final int line = layout.getLineForOffset(offset);
         final int top = layout.getLineTop(line);
-        final int bottom = layout.getLineBottomWithoutSpacing(line);
+        final int bottom = layout.getLineBottom(line, /* includeLineSpacing= */ false);
 
         final boolean clamped = layout.shouldClampCursor(line);
         updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(offset, clamped));
@@ -3443,7 +3443,7 @@
         @Override
         protected int getVerticalLocalPosition(int line) {
             final Layout layout = mTextView.getLayout();
-            return layout.getLineBottomWithoutSpacing(line);
+            return layout.getLineBottom(line, /* includeLineSpacing= */ false);
         }
 
         @Override
@@ -4109,7 +4109,8 @@
         @Override
         protected int getVerticalLocalPosition(int line) {
             final Layout layout = mTextView.getLayout();
-            return layout.getLineBottomWithoutSpacing(line) - mContainerMarginTop;
+            return layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                    - mContainerMarginTop;
         }
 
         @Override
@@ -4706,8 +4707,9 @@
                                 + viewportToContentVerticalOffset;
                         final float insertionMarkerBaseline = layout.getLineBaseline(line)
                                 + viewportToContentVerticalOffset;
-                        final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line)
-                                + viewportToContentVerticalOffset;
+                        final float insertionMarkerBottom =
+                                layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                                        + viewportToContentVerticalOffset;
                         final boolean isTopVisible = mTextView
                                 .isPositionVisible(insertionMarkerX, insertionMarkerTop);
                         final boolean isBottomVisible = mTextView
@@ -5137,7 +5139,7 @@
 
                 mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
                         - getHorizontalOffset() + getCursorOffset();
-                mPositionY = layout.getLineBottomWithoutSpacing(line);
+                mPositionY = layout.getLineBottom(line, /* includeLineSpacing= */ false);
 
                 // Take TextView's padding and scroll into account.
                 mPositionX += mTextView.viewportToContentHorizontalOffset();
@@ -5233,8 +5235,8 @@
             if (mNewMagnifierEnabled) {
                 Layout layout = mTextView.getLayout();
                 final int line = layout.getLineForOffset(getCurrentCursorOffset());
-                return layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line)
-                        >= mMaxLineHeightForMagnifier;
+                return layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                        - layout.getLineTop(line) >= mMaxLineHeightForMagnifier;
             }
             final float magnifierContentHeight = Math.round(
                     mMagnifierAnimator.mMagnifier.getHeight()
@@ -5389,7 +5391,8 @@
 
             // Vertically snap to middle of current line.
             showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber)
-                    + mTextView.getLayout().getLineBottomWithoutSpacing(lineNumber)) / 2.0f
+                    + mTextView.getLayout()
+                            .getLineBottom(lineNumber, /* includeLineSpacing= */ false)) / 2.0f
                     + mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY;
             return true;
         }
@@ -5473,7 +5476,8 @@
                         updateCursorPosition();
                     }
                     final int lineHeight =
-                            layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+                            layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                                    - layout.getLineTop(line);
                     float zoom = mInitialZoom;
                     if (lineHeight < mMinLineHeightForMagnifier) {
                         zoom = zoom * mMinLineHeightForMagnifier / lineHeight;
@@ -5823,8 +5827,8 @@
         private MotionEvent transformEventForTouchThrough(MotionEvent ev) {
             final Layout layout = mTextView.getLayout();
             final int line = layout.getLineForOffset(getCurrentCursorOffset());
-            final int textHeight =
-                    layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
+            final int textHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                    - layout.getLineTop(line);
             // Transforms the touch events to screen coordinates.
             // And also shift up to make the hit point is on the text.
             // Note:
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 56310ae..02dafd4 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -8,8 +8,8 @@
 mount@google.com
 njawad@google.com
 
-per-file TextView*,EditText.java,Editor.java,EditorTouchState.java = file:../text/OWNERS
+per-file TextView*,Edit*,Selection* = file:../text/OWNERS
 
 per-file SpellChecker.java = file:../view/inputmethod/OWNERS
 
-per-file RemoteViews* = file:../appwidget/OWNERS
\ No newline at end of file
+per-file RemoteViews* = file:../appwidget/OWNERS
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 68b902f..b339d76 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -99,14 +99,14 @@
 import android.text.DynamicLayout;
 import android.text.Editable;
 import android.text.GetChars;
-import android.text.GraphemeClusterSegmentIterator;
+import android.text.GraphemeClusterSegmentFinder;
 import android.text.GraphicsOperations;
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
 import android.text.ParcelableSpan;
 import android.text.PrecomputedText;
-import android.text.SegmentIterator;
+import android.text.SegmentFinder;
 import android.text.Selection;
 import android.text.SpanWatcher;
 import android.text.Spannable;
@@ -120,7 +120,7 @@
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.TextWatcher;
-import android.text.WordSegmentIterator;
+import android.text.WordSegmentFinder;
 import android.text.method.AllCapsTransformationMethod;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.DateKeyListener;
@@ -195,6 +195,8 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
@@ -238,6 +240,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * A user interface element that displays text to the user.
@@ -1033,6 +1037,8 @@
     //
     // End of autofill-related attributes
 
+    private Pattern mWhitespacePattern;
+
     /**
      * Kick-start the font cache for the zygote process (to pay the cost of
      * initializing freetype for our default font only once).
@@ -7644,7 +7650,8 @@
         createEditorIfNeeded();
         mEditor.setError(error, icon);
         notifyViewAccessibilityStateChangedIfNeeded(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_INVALID);
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
+                        | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
     }
 
     @Override
@@ -9090,6 +9097,8 @@
                 gestures.add(SelectGesture.class);
                 gestures.add(DeleteGesture.class);
                 gestures.add(InsertGesture.class);
+                gestures.add(RemoveSpaceGesture.class);
+                gestures.add(JoinOrSplitGesture.class);
                 outAttrs.setSupportedHandwritingGestures(gestures);
                 return ic;
             }
@@ -9303,7 +9312,9 @@
 
     /** @hide */
     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
-        int[] range = getRangeForRect(gesture.getSelectionArea(), gesture.getGranularity());
+        int[] range = getRangeForRect(
+                convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
+                gesture.getGranularity());
         if (range == null) {
             return handleGestureFailure(gesture);
         }
@@ -9314,28 +9325,74 @@
 
     /** @hide */
     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
-        int[] range = getRangeForRect(gesture.getDeletionArea(), gesture.getGranularity());
+        int[] range = getRangeForRect(
+                convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
+                gesture.getGranularity());
         if (range == null) {
             return handleGestureFailure(gesture);
         }
-        getEditableText().delete(range[0], range[1]);
-        Selection.setSelection(getEditableText(), range[0]);
-        // TODO(b/243983058): Delete extra spaces.
+        int start = range[0];
+        int end = range[1];
+
+        // For word granularity, adjust the start and end offsets to remove extra whitespace around
+        // the deleted text.
+        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
+            // If the deleted text is at the start of the text, the behavior is the same as the case
+            // where the deleted text follows a new line character.
+            int codePointBeforeStart = start > 0
+                    ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
+            // If the deleted text is at the end of the text, the behavior is the same as the case
+            // where the deleted text precedes a new line character.
+            int codePointAtEnd = end < mText.length()
+                    ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
+            if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
+                    && (TextUtils.isWhitespace(codePointAtEnd)
+                            || TextUtils.isPunctuation(codePointAtEnd))) {
+                // Remove whitespace (except new lines) before the deleted text, in these cases:
+                // - There is whitespace following the deleted text
+                //     e.g. "one [deleted] three" -> "one | three" -> "one| three"
+                // - There is punctuation following the deleted text
+                //     e.g. "one [deleted]!" -> "one |!" -> "one|!"
+                // - There is a new line following the deleted text
+                //     e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
+                // - The deleted text is at the end of the text
+                //     e.g. "one [deleted]" -> "one |" -> "one|"
+                // (The pipe | indicates the cursor position.)
+                do {
+                    start -= Character.charCount(codePointBeforeStart);
+                    if (start == 0) break;
+                    codePointBeforeStart = Character.codePointBefore(mText, start);
+                } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
+            } else if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
+                    && (TextUtils.isWhitespace(codePointBeforeStart)
+                            || TextUtils.isPunctuation(codePointBeforeStart))) {
+                // Remove whitespace (except new lines) after the deleted text, in these cases:
+                // - There is punctuation preceding the deleted text
+                //     e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
+                // - There is a new line preceding the deleted text
+                //     e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
+                // - The deleted text is at the start of the text
+                //     e.g. "[deleted] two" -> "| two" -> "|two"
+                // (The pipe | indicates the cursor position.)
+                do {
+                    end += Character.charCount(codePointAtEnd);
+                    if (end == mText.length()) break;
+                    codePointAtEnd = Character.codePointAt(mText, end);
+                } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
+            }
+        }
+
+        getEditableText().delete(start, end);
+        Selection.setSelection(getEditableText(), start);
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
     /** @hide */
     public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
-        PointF point = gesture.getInsertionPoint();
-        // The coordinates provided are screen coordinates - transform to content coordinates.
-        int[] screenToViewport = getLocationOnScreen();
-        point.offset(
-                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
-                -(screenToViewport[1] + viewportToContentVerticalOffset()));
-
+        PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
         int line = mLayout.getLineForVertical((int) point.y);
         if (point.y < mLayout.getLineTop(line)
-                || point.y > mLayout.getLineBottomWithoutSpacing(line)) {
+                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
             return handleGestureFailure(gesture);
         }
         if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
@@ -9349,6 +9406,113 @@
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
+    /** @hide */
+    public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
+        PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
+        PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
+
+        // The operation should be applied to the first line of text touched by the line joining
+        // the points.
+        int yMin = (int) Math.min(startPoint.y, endPoint.y);
+        int yMax = (int) Math.max(startPoint.y, endPoint.y);
+        int line = mLayout.getLineForVertical(yMin);
+        if (yMax < mLayout.getLineTop(line)) {
+            // Both points are above the top of the first line.
+            return handleGestureFailure(gesture);
+        }
+        if (yMin > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
+            if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) {
+                // The points are below the last line, or they are between two lines.
+                return handleGestureFailure(gesture);
+            } else {
+                // Apply the operation to the next line.
+                line++;
+            }
+        }
+        if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line)
+                || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) {
+            return handleGestureFailure(gesture);
+        }
+
+        // The operation should be applied to all characters touched by the line joining the points.
+        int startOffset = mLayout.getOffsetForHorizontal(line, startPoint.x);
+        int endOffset = mLayout.getOffsetForHorizontal(line, endPoint.x);
+        if (startOffset == endOffset) {
+            return handleGestureFailure(gesture);
+        } else if (startOffset > endOffset) {
+            int tmp = startOffset;
+            startOffset = endOffset;
+            endOffset = tmp;
+        }
+        // TODO(b/247557062): The boundary offsets might be off by one. We should check which side
+        // of the offset the point is on, and adjust if necessary.
+        // TODO(b/247557062): This doesn't handle bidirectional text correctly.
+
+        Pattern whitespacePattern = getWhitespacePattern();
+        Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
+        int lastRemoveOffset = -1;
+        while (matcher.find()) {
+            lastRemoveOffset = startOffset + matcher.start();
+            getEditableText().delete(lastRemoveOffset, startOffset + matcher.end());
+            startOffset = lastRemoveOffset;
+            endOffset -= matcher.end() - matcher.start();
+            if (startOffset == endOffset) {
+                break;
+            }
+            matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset));
+        }
+        if (lastRemoveOffset == -1) {
+            return handleGestureFailure(gesture);
+        }
+        Selection.setSelection(getEditableText(), lastRemoveOffset);
+        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+    }
+
+    /** @hide */
+    public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
+        PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
+
+        int line = mLayout.getLineForVertical((int) point.y);
+        if (point.y < mLayout.getLineTop(line)
+                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
+            return handleGestureFailure(gesture);
+        }
+        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
+            return handleGestureFailure(gesture);
+        }
+
+        int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
+        if (mLayout.isLevelBoundary(startOffset)) {
+            // TODO(b/247551937): Support gesture at level boundaries.
+            return handleGestureFailure(gesture);
+        }
+
+        int endOffset = startOffset;
+        while (startOffset > 0) {
+            int codePointBeforeStart = Character.codePointBefore(mText, startOffset);
+            if (!TextUtils.isWhitespace(codePointBeforeStart)) {
+                break;
+            }
+            startOffset -= Character.charCount(codePointBeforeStart);
+        }
+        while (endOffset < mText.length()) {
+            int codePointAtEnd = Character.codePointAt(mText, endOffset);
+            if (!TextUtils.isWhitespace(codePointAtEnd)) {
+                break;
+            }
+            endOffset += Character.charCount(codePointAtEnd);
+        }
+        if (startOffset < endOffset) {
+            getEditableText().delete(startOffset, endOffset);
+            Selection.setSelection(getEditableText(), startOffset);
+        } else {
+            // No whitespace found, so insert a space.
+            getEditableText().insert(startOffset, " ");
+            Selection.setSelection(getEditableText(), startOffset + 1);
+        }
+        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+    }
+
     private int handleGestureFailure(HandwritingGesture gesture) {
         if (!TextUtils.isEmpty(gesture.getFallbackText())) {
             getEditableText()
@@ -9360,23 +9524,24 @@
 
     @Nullable
     private int[] getRangeForRect(@NonNull RectF area, int granularity) {
-        // The coordinates provided are screen coordinates - transform to content coordinates.
-        int[] screenToViewport = getLocationOnScreen();
-        area = new RectF(area);
-        area.offset(
-                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
-                -(screenToViewport[1] + viewportToContentVerticalOffset()));
-
-        SegmentIterator segmentIterator;
+        SegmentFinder segmentFinder;
         if (granularity == HandwritingGesture.GRANULARITY_WORD) {
             WordIterator wordIterator = getWordIterator();
             wordIterator.setCharSequence(mText, 0, mText.length());
-            segmentIterator = new WordSegmentIterator(mText, wordIterator);
+            segmentFinder = new WordSegmentFinder(mText, wordIterator);
         } else {
-            segmentIterator = new GraphemeClusterSegmentIterator(mText, mTextPaint);
+            segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint);
         }
 
-        return mLayout.getRangeForRect(area, segmentIterator);
+        return mLayout.getRangeForRect(
+                area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
+    }
+
+    private Pattern getWhitespacePattern() {
+        if (mWhitespacePattern == null) {
+            mWhitespacePattern = Pattern.compile("\\s+");
+        }
+        return mWhitespacePattern;
     }
 
     /** @hide */
@@ -10631,6 +10796,24 @@
         r.bottom += verticalOffset;
     }
 
+    private PointF convertFromScreenToContentCoordinates(PointF point) {
+        int[] screenToViewport = getLocationOnScreen();
+        PointF copy = new PointF(point);
+        copy.offset(
+                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
+                -(screenToViewport[1] + viewportToContentVerticalOffset()));
+        return copy;
+    }
+
+    private RectF convertFromScreenToContentCoordinates(RectF rect) {
+        int[] screenToViewport = getLocationOnScreen();
+        RectF copy = new RectF(rect);
+        copy.offset(
+                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
+                -(screenToViewport[1] + viewportToContentVerticalOffset()));
+        return copy;
+    }
+
     int viewportToContentHorizontalOffset() {
         return getCompoundPaddingLeft() - mScrollX;
     }
@@ -12049,6 +12232,11 @@
         }
     }
 
+    @Override
+    public boolean isAutoHandwritingEnabled() {
+        return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
+    }
+
     /** @hide */
     @Override
     public boolean isStylusHandwritingAvailable() {
diff --git a/core/java/android/app/cloudsearch/SearchResult.aidl b/core/java/android/window/BackAnimationAdapter.aidl
similarity index 74%
copy from core/java/android/app/cloudsearch/SearchResult.aidl
copy to core/java/android/window/BackAnimationAdapter.aidl
index daebfbf..2d7126c 100644
--- a/core/java/android/app/cloudsearch/SearchResult.aidl
+++ b/core/java/android/window/BackAnimationAdapter.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
+/*
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.app.cloudsearch;
+package android.window;
 
-parcelable SearchResult;
\ No newline at end of file
+/**
+ * @hide
+ */
+parcelable BackAnimationAdapter;
\ No newline at end of file
diff --git a/core/java/android/window/BackAnimationAdapter.java b/core/java/android/window/BackAnimationAdapter.java
new file mode 100644
index 0000000..5eb34e6
--- /dev/null
+++ b/core/java/android/window/BackAnimationAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.window;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Object that describes how to run a remote back animation.
+ *
+ * @hide
+ */
+public class BackAnimationAdapter implements Parcelable {
+    private final IBackAnimationRunner mRunner;
+
+    public BackAnimationAdapter(IBackAnimationRunner runner) {
+        mRunner = runner;
+    }
+
+    public BackAnimationAdapter(Parcel in) {
+        mRunner = IBackAnimationRunner.Stub.asInterface(in.readStrongBinder());
+    }
+
+    public IBackAnimationRunner getRunner() {
+        return mRunner;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mRunner);
+    }
+
+    public static final @android.annotation.NonNull Creator<BackAnimationAdapter> CREATOR =
+            new Creator<BackAnimationAdapter>() {
+        public BackAnimationAdapter createFromParcel(Parcel in) {
+            return new BackAnimationAdapter(in);
+        }
+
+        public BackAnimationAdapter[] newArray(int size) {
+            return new BackAnimationAdapter[size];
+        }
+    };
+}
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1024e2e..4a4f561 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -18,10 +18,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -52,8 +50,6 @@
 
     @SwipeEdge
     private final int mSwipeEdge;
-    @Nullable
-    private final RemoteAnimationTarget mDepartingAnimationTarget;
 
     /**
      * Creates a new {@link BackEvent} instance.
@@ -62,16 +58,12 @@
      * @param touchY Absolute Y location of the touch point of this event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
      * @param swipeEdge Indicates which edge the swipe starts from.
-     * @param departingAnimationTarget The remote animation target of the departing application
-     *                                 window.
      */
-    public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
-            @Nullable RemoteAnimationTarget departingAnimationTarget) {
+    public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
         mTouchX = touchX;
         mTouchY = touchY;
         mProgress = progress;
         mSwipeEdge = swipeEdge;
-        mDepartingAnimationTarget = departingAnimationTarget;
     }
 
     private BackEvent(@NonNull Parcel in) {
@@ -79,7 +71,6 @@
         mTouchY = in.readFloat();
         mProgress = in.readFloat();
         mSwipeEdge = in.readInt();
-        mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
     }
 
     public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
@@ -105,7 +96,6 @@
         dest.writeFloat(mTouchY);
         dest.writeFloat(mProgress);
         dest.writeInt(mSwipeEdge);
-        dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
 
     /**
@@ -136,16 +126,6 @@
         return mSwipeEdge;
     }
 
-    /**
-     * Returns the {@link RemoteAnimationTarget} of the top departing application window,
-     * or {@code null} if the top window should not be moved for the current type of back
-     * destination.
-     */
-    @Nullable
-    public RemoteAnimationTarget getDepartingAnimationTarget() {
-        return mDepartingAnimationTarget;
-    }
-
     @Override
     public String toString() {
         return "BackEvent{"
@@ -153,7 +133,6 @@
                 + ", mTouchY=" + mTouchY
                 + ", mProgress=" + mProgress
                 + ", mSwipeEdge" + mSwipeEdge
-                + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
                 + "}";
     }
 }
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index dd49014..9b91cf2 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -19,14 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.WindowConfiguration;
-import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteCallback;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
 
 /**
  * Information to be sent to SysUI about a back event.
@@ -84,75 +80,57 @@
             TYPE_CROSS_TASK,
             TYPE_CALLBACK
     })
-    @interface BackTargetType {
+    public @interface BackTargetType {
     }
 
     private final int mType;
     @Nullable
-    private final RemoteAnimationTarget mDepartingAnimationTarget;
-    @Nullable
-    private final SurfaceControl mScreenshotSurface;
-    @Nullable
-    private final HardwareBuffer mScreenshotBuffer;
-    @Nullable
     private final RemoteCallback mOnBackNavigationDone;
     @Nullable
-    private final WindowConfiguration mTaskWindowConfiguration;
-    @Nullable
     private final IOnBackInvokedCallback mOnBackInvokedCallback;
+    private final boolean mPrepareRemoteAnimation;
+    @Nullable
+    private WindowContainerToken mDepartingWindowContainerToken;
 
     /**
      * Create a new {@link BackNavigationInfo} instance.
      *
      * @param type                    The {@link BackTargetType} of the destination (what will be
-     *                                displayed after the back action).
-     * @param departingAnimationTarget  The remote animation target, containing a leash to animate
-     *                                  away the departing window. The consumer of the leash is
-     *                                  responsible for removing it.
-     * @param screenshotSurface       The screenshot of the previous activity to be displayed.
-     * @param screenshotBuffer        A buffer containing a screenshot used to display the activity.
-     *                                See {@link  #getScreenshotHardwareBuffer()} for information
-     *                                about nullity.
-     * @param taskWindowConfiguration The window configuration of the Task being animated beneath.
      * @param onBackNavigationDone    The callback to be called once the client is done with the
      *                                back preview.
      * @param onBackInvokedCallback   The back callback registered by the current top level window.
+     * @param departingWindowContainerToken The {@link WindowContainerToken} of departing window.
+     * @param isPrepareRemoteAnimation  Return whether the core is preparing a back gesture
+     *                                  animation, if true, the caller of startBackNavigation should
+     *                                  be expected to receive an animation start callback.
      */
     private BackNavigationInfo(@BackTargetType int type,
-            @Nullable RemoteAnimationTarget departingAnimationTarget,
-            @Nullable SurfaceControl screenshotSurface,
-            @Nullable HardwareBuffer screenshotBuffer,
-            @Nullable WindowConfiguration taskWindowConfiguration,
             @Nullable RemoteCallback onBackNavigationDone,
-            @Nullable IOnBackInvokedCallback onBackInvokedCallback) {
+            @Nullable IOnBackInvokedCallback onBackInvokedCallback,
+            boolean isPrepareRemoteAnimation,
+            @Nullable WindowContainerToken departingWindowContainerToken) {
         mType = type;
-        mDepartingAnimationTarget = departingAnimationTarget;
-        mScreenshotSurface = screenshotSurface;
-        mScreenshotBuffer = screenshotBuffer;
-        mTaskWindowConfiguration = taskWindowConfiguration;
         mOnBackNavigationDone = onBackNavigationDone;
         mOnBackInvokedCallback = onBackInvokedCallback;
+        mPrepareRemoteAnimation = isPrepareRemoteAnimation;
+        mDepartingWindowContainerToken = departingWindowContainerToken;
     }
 
     private BackNavigationInfo(@NonNull Parcel in) {
         mType = in.readInt();
-        mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
-        mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR);
-        mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR);
-        mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
         mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
         mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
+        mPrepareRemoteAnimation = in.readBoolean();
+        mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
-        dest.writeTypedObject(mDepartingAnimationTarget, flags);
-        dest.writeTypedObject(mScreenshotSurface, flags);
-        dest.writeTypedObject(mScreenshotBuffer, flags);
-        dest.writeTypedObject(mTaskWindowConfiguration, flags);
         dest.writeTypedObject(mOnBackNavigationDone, flags);
         dest.writeStrongInterface(mOnBackInvokedCallback);
+        dest.writeBoolean(mPrepareRemoteAnimation);
+        dest.writeTypedObject(mDepartingWindowContainerToken, flags);
     }
 
     /**
@@ -165,49 +143,6 @@
     }
 
     /**
-     * Returns a {@link RemoteAnimationTarget}, containing a leash to the top window container
-     * that needs to be animated. This can be null if the back animation is controlled by
-     * the application.
-     */
-    @Nullable
-    public RemoteAnimationTarget getDepartingAnimationTarget() {
-        return mDepartingAnimationTarget;
-    }
-
-    /**
-     * Returns the {@link SurfaceControl} that should be used to display a screenshot of the
-     * previous activity.
-     */
-    @Nullable
-    public SurfaceControl getScreenshotSurface() {
-        return mScreenshotSurface;
-    }
-
-    /**
-     * Returns the {@link HardwareBuffer} containing the screenshot the activity about to be
-     * shown. This can be null if one of the following conditions is met:
-     * <ul>
-     *     <li>The screenshot is not available
-     *     <li> The previous activity is the home screen ( {@link  #TYPE_RETURN_TO_HOME}
-     *     <li> The current window is a dialog ({@link  #TYPE_DIALOG_CLOSE}
-     *     <li> The back animation is controlled by the application
-     * </ul>
-     */
-    @Nullable
-    public HardwareBuffer getScreenshotHardwareBuffer() {
-        return mScreenshotBuffer;
-    }
-
-    /**
-     * Returns the {@link WindowConfiguration} of the current task. This is null when the top
-     * application is controlling the back animation.
-     */
-    @Nullable
-    public WindowConfiguration getTaskWindowConfiguration() {
-        return mTaskWindowConfiguration;
-    }
-
-    /**
      * Returns the {@link OnBackInvokedCallback} of the top level window or null if
      * the client didn't register a callback.
      * <p>
@@ -222,6 +157,25 @@
     }
 
     /**
+     * Return true if the core is preparing a back gesture nimation.
+     */
+    public boolean isPrepareRemoteAnimation() {
+        return mPrepareRemoteAnimation;
+    }
+
+    /**
+     * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
+     * removed.
+     * <p>
+     * For example, if an Activity is the last one of its Task, the Task's token will be given.
+     * Otherwise, it will be the Activity's token.
+     */
+    @Nullable
+    public WindowContainerToken getDepartingWindowContainerToken() {
+        return mDepartingWindowContainerToken;
+    }
+
+    /**
      * Callback to be called when the back preview is finished in order to notify the server that
      * it can clean up the resources created for the animation.
      *
@@ -256,12 +210,9 @@
     public String toString() {
         return "BackNavigationInfo{"
                 + "mType=" + typeToString(mType) + " (" + mType + ")"
-                + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
-                + ", mScreenshotSurface=" + mScreenshotSurface
-                + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration
-                + ", mScreenshotBuffer=" + mScreenshotBuffer
                 + ", mOnBackNavigationDone=" + mOnBackNavigationDone
                 + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+                + ", mWindowContainerToken=" + mDepartingWindowContainerToken
                 + '}';
     }
 
@@ -291,20 +242,14 @@
      */
     @SuppressWarnings("UnusedReturnValue") // Builder pattern
     public static class Builder {
-
         private int mType = TYPE_UNDEFINED;
         @Nullable
-        private RemoteAnimationTarget mDepartingAnimationTarget = null;
-        @Nullable
-        private SurfaceControl mScreenshotSurface = null;
-        @Nullable
-        private HardwareBuffer mScreenshotBuffer = null;
-        @Nullable
-        private WindowConfiguration mTaskWindowConfiguration = null;
-        @Nullable
         private RemoteCallback mOnBackNavigationDone = null;
         @Nullable
         private IOnBackInvokedCallback mOnBackInvokedCallback = null;
+        private boolean mPrepareRemoteAnimation;
+        @Nullable
+        private WindowContainerToken mDepartingWindowContainerToken = null;
 
         /**
          * @see BackNavigationInfo#getType()
@@ -315,40 +260,6 @@
         }
 
         /**
-         * @see BackNavigationInfo#getDepartingAnimationTarget
-         */
-        public Builder setDepartingAnimationTarget(
-                @Nullable RemoteAnimationTarget departingAnimationTarget) {
-            mDepartingAnimationTarget = departingAnimationTarget;
-            return this;
-        }
-
-        /**
-         * @see BackNavigationInfo#getScreenshotSurface
-         */
-        public Builder setScreenshotSurface(@Nullable SurfaceControl screenshotSurface) {
-            mScreenshotSurface = screenshotSurface;
-            return this;
-        }
-
-        /**
-         * @see BackNavigationInfo#getScreenshotHardwareBuffer()
-         */
-        public Builder setScreenshotBuffer(@Nullable HardwareBuffer screenshotBuffer) {
-            mScreenshotBuffer = screenshotBuffer;
-            return this;
-        }
-
-        /**
-         * @see BackNavigationInfo#getTaskWindowConfiguration
-         */
-        public Builder setTaskWindowConfiguration(
-                @Nullable WindowConfiguration taskWindowConfiguration) {
-            mTaskWindowConfiguration = taskWindowConfiguration;
-            return this;
-        }
-
-        /**
          * @see BackNavigationInfo#onBackNavigationFinished(boolean)
          */
         public Builder setOnBackNavigationDone(@Nullable RemoteCallback onBackNavigationDone) {
@@ -366,12 +277,28 @@
         }
 
         /**
+         * @param prepareRemoteAnimation Whether core prepare animation for shell.
+         */
+        public Builder setPrepareRemoteAnimation(boolean prepareRemoteAnimation) {
+            mPrepareRemoteAnimation = prepareRemoteAnimation;
+            return this;
+        }
+
+        /**
+         * @see BackNavigationInfo#getDepartingWindowContainerToken()
+         */
+        public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
+            mDepartingWindowContainerToken = windowContainerToken;
+        }
+
+        /**
          * Builds and returns an instance of {@link BackNavigationInfo}
          */
         public BackNavigationInfo build() {
-            return new BackNavigationInfo(mType, mDepartingAnimationTarget, mScreenshotSurface,
-                    mScreenshotBuffer, mTaskWindowConfiguration, mOnBackNavigationDone,
-                    mOnBackInvokedCallback);
+            return new BackNavigationInfo(mType, mOnBackNavigationDone,
+                    mOnBackInvokedCallback,
+                    mPrepareRemoteAnimation,
+                    mDepartingWindowContainerToken);
         }
     }
 }
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl
similarity index 63%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to core/java/android/window/IBackAnimationFinishedCallback.aidl
index 104bf99..8afc003 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl
@@ -11,18 +11,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.service.cloudsearch;
-
-import android.app.cloudsearch.SearchRequest;
+package android.window;
 
 /**
- * Interface from the system to CloudSearch service.
+ * Interface to be invoked by the controlling process when a back animation has finished.
  *
- * @hide
+ * @param trigger Whether the back gesture has passed the triggering threshold.
+ * {@hide}
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+oneway interface IBackAnimationFinishedCallback {
+    void onAnimationFinished(in boolean triggerBack);
+}
\ No newline at end of file
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
new file mode 100644
index 0000000..1c67789
--- /dev/null
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.window;
+
+import android.view.RemoteAnimationTarget;
+import android.window.IBackAnimationFinishedCallback;
+
+/**
+ * Interface that is used to callback from window manager to the process that runs a back gesture
+ * animation to start or cancel it.
+ *
+ * {@hide}
+ */
+oneway interface IBackAnimationRunner {
+
+    /**
+     * Called when the system needs to cancel the current animation. This can be due to the
+     * wallpaper not drawing in time, or the handler not finishing the animation within a predefined
+     * amount of time.
+     *
+     */
+    void onAnimationCancelled() = 1;
+
+    /**
+     * Called when the system is ready for the handler to start animating all the visible tasks.
+     * @param type The back navigation type.
+     * @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
+                   windows to animate,
+     * @param wallpapers The list of wallpapers to animate.
+     * @param nonApps The list of non-app windows such as Bubbles to animate.
+     * @param finishedCallback The callback to invoke when the animation is finished.
+     */
+    void onAnimationStart(in int type,
+            in RemoteAnimationTarget[] apps,
+            in RemoteAnimationTarget[] wallpapers,
+            in RemoteAnimationTarget[] nonApps,
+            in IBackAnimationFinishedCallback finishedCallback) = 2;
+}
\ No newline at end of file
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 3c7cd02..36eaf49 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -51,16 +51,19 @@
             in IWindowContainerTransactionCallback callback);
 
     /**
-     * Starts a transition.
+     * Starts a new transition.
      * @param type The transition type.
-     * @param transitionToken A token associated with the transition to start. If null, a new
-     *                        transition will be created of the provided type.
      * @param t Operations that are part of the transition.
-     * @return a token representing the transition. This will just be transitionToken if it was
-     *         non-null.
+     * @return a token representing the transition.
      */
-    IBinder startTransition(int type, in @nullable IBinder transitionToken,
-            in @nullable WindowContainerTransaction t);
+    IBinder startNewTransition(int type, in @nullable WindowContainerTransaction t);
+
+    /**
+     * Starts the given transition.
+     * @param transitionToken A token associated with the transition to start.
+     * @param t Operations that are part of the transition.
+     */
+    oneway void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
 
     /**
      * Starts a legacy transition.
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index b8f39a9..8a7efb9 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+import android.util.Pair;
 import android.view.SurfaceControl;
 
 import libcore.util.NativeAllocationRegistry;
@@ -42,6 +43,7 @@
  */
 public class ScreenCapture {
     private static final String TAG = "ScreenCapture";
+    private static final int SCREENSHOT_WAIT_TIME_S = 1;
 
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
             long captureListener);
@@ -71,13 +73,17 @@
      */
     public static ScreenshotHardwareBuffer captureDisplay(
             DisplayCaptureArgs captureArgs) {
-        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
-        int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener());
+        Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture = createSyncCaptureListener();
+        int status = captureDisplay(captureArgs, syncScreenCapture.first);
         if (status != 0) {
             return null;
         }
 
-        return screenCaptureListener.waitForScreenshot();
+        try {
+            return syncScreenCapture.second.get();
+        } catch (Exception e) {
+            return null;
+        }
     }
 
     /**
@@ -126,16 +132,18 @@
     /**
      * @hide
      */
-    public static ScreenshotHardwareBuffer captureLayers(
-            LayerCaptureArgs captureArgs) {
-        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
-
-        int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener());
+    public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
+        Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture = createSyncCaptureListener();
+        int status = captureLayers(captureArgs, syncScreenCapture.first);
         if (status != 0) {
             return null;
         }
 
-        return screenCaptureListener.waitForScreenshot();
+        try {
+            return syncScreenCapture.second.get();
+        } catch (Exception e) {
+            return null;
+        }
     }
 
     /**
@@ -609,6 +617,8 @@
      * The object used to receive the results when invoking screen capture requests via
      * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
      * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
+     *
+     * This listener can only be used for a single call to capture content call.
      */
     public static class ScreenCaptureListener implements Parcelable {
         private final long mNativeObject;
@@ -663,45 +673,46 @@
     }
 
     /**
-     * A helper class to handle the async screencapture callbacks synchronously. This should only
+     * A helper method to handle the async screencapture callbacks synchronously. This should only
      * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
+     *
+     * @return a Pair that holds the {@link ScreenCaptureListener} that should be used for capture
+     * calls into SurfaceFlinger and a {@link ScreenshotSync} object to retrieve the results.
      */
-    public static class SyncScreenCaptureListener {
-        private static final int SCREENSHOT_WAIT_TIME_S = 1;
-        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+    public static Pair<ScreenCaptureListener, ScreenshotSync> createSyncCaptureListener() {
+        final ScreenshotSync screenshotSync = new ScreenshotSync();
+        final ScreenCaptureListener screenCaptureListener = new ScreenCaptureListener(
+                screenshotSync::setScreenshotHardwareBuffer);
+        return new Pair<>(screenCaptureListener, screenshotSync);
+    }
+
+    /**
+     * Helper class to synchronously get the {@link ScreenshotHardwareBuffer} when calling
+     * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} or
+     * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)}
+     */
+    public static class ScreenshotSync {
         private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-        private final ScreenCaptureListener mScreenCaptureListener;
+        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
 
-        public SyncScreenCaptureListener() {
-            mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> {
-                mScreenshotHardwareBuffer = screenshotHardwareBuffer;
-                mCountDownLatch.countDown();
-            });
+        private void setScreenshotHardwareBuffer(
+                ScreenshotHardwareBuffer screenshotHardwareBuffer) {
+            mScreenshotHardwareBuffer = screenshotHardwareBuffer;
+            mCountDownLatch.countDown();
         }
 
         /**
-         * @return The underlying {@link ScreenCaptureListener}
+         * Get the {@link ScreenshotHardwareBuffer} synchronously. This can be null if the
+         * screenshot failed or if there was no callback in {@link #SCREENSHOT_WAIT_TIME_S} seconds.
          */
-        public ScreenCaptureListener getScreenCaptureListener() {
-            return mScreenCaptureListener;
-        }
-
-        /**
-         * Waits until the screenshot callback has been invoked and the screenshot is ready. This
-         * can return {@code null} if the screenshot callback wasn't invoked after
-         * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error
-         *
-         * @return A ScreenshotHardwareBuffer for the content that was captured.
-         */
-        @Nullable
-        public ScreenshotHardwareBuffer waitForScreenshot() {
+        public ScreenshotHardwareBuffer get() {
             try {
                 mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+                return mScreenshotHardwareBuffer;
             } catch (Exception e) {
                 Log.e(TAG, "Failed to wait for screen capture result", e);
+                return null;
             }
-
-            return mScreenshotHardwareBuffer;
         }
     }
 }
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index 56e9107..e2c8a31 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -188,6 +188,10 @@
     /**
      * Returns {@code true} if the parameters that are important for task fragment organizers are
      * equal between this {@link TaskFragmentInfo} and {@param that}.
+     * Note that this method is usually called with
+     * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+     * Configuration, Configuration)} to determine if this {@link TaskFragmentInfo} should
+     * be dispatched to the client.
      */
     public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) {
         if (that == null) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/core/java/android/window/TaskFragmentParentInfo.aidl
similarity index 73%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
copy to core/java/android/window/TaskFragmentParentInfo.aidl
index f9b0800..79d2209 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ b/core/java/android/window/TaskFragmentParentInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker.pip
+package android.window;
 
-internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+parcelable TaskFragmentParentInfo;
\ No newline at end of file
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
new file mode 100644
index 0000000..64b2638
--- /dev/null
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+public class TaskFragmentParentInfo implements Parcelable {
+    @NonNull
+    private final Configuration mConfiguration = new Configuration();
+
+    private final int mDisplayId;
+
+    private final boolean mVisibleRequested;
+
+    public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
+            boolean visibleRequested) {
+        mConfiguration.setTo(configuration);
+        mDisplayId = displayId;
+        mVisibleRequested = visibleRequested;
+    }
+
+    public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+        mConfiguration.setTo(info.getConfiguration());
+        mDisplayId = info.mDisplayId;
+        mVisibleRequested = info.mVisibleRequested;
+    }
+
+    /** The {@link Configuration} of the parent Task */
+    @NonNull
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    /**
+     * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
+     * Task is detached from previously associated display.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /** Whether the parent Task is requested to be visible or not */
+    public boolean isVisibleRequested() {
+        return mVisibleRequested;
+    }
+
+    /**
+     * Returns {@code true} if the parameters which are important for task fragment
+     * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}.
+     * Note that this method is usually called with
+     * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+     * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
+     * be dispatched to the client.
+     */
+    public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
+        if (that == null) {
+            return false;
+        }
+        return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
+                && mVisibleRequested == that.mVisibleRequested;
+    }
+
+    @WindowConfiguration.WindowingMode
+    private int getWindowingMode() {
+        return mConfiguration.windowConfiguration.getWindowingMode();
+    }
+
+    @Override
+    public String toString() {
+        return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+                + "config=" + mConfiguration
+                + ", displayId=" + mDisplayId
+                + ", visibleRequested=" + mVisibleRequested
+                + "}";
+    }
+
+    /**
+     * Indicates that whether this {@link TaskFragmentParentInfo} equals to {@code obj}.
+     * Note that {@link #equalsForTaskFragmentOrganizer(TaskFragmentParentInfo)} should be used
+     * for most cases because not all {@link Configuration} properties are interested for
+     * {@link TaskFragmentOrganizer}.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof TaskFragmentParentInfo)) {
+            return false;
+        }
+        final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
+        return mConfiguration.equals(that.mConfiguration)
+                && mDisplayId == that.mDisplayId
+                && mVisibleRequested == that.mVisibleRequested;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mConfiguration.hashCode();
+        result = 31 * result + mDisplayId;
+        result = 31 * result + (mVisibleRequested ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mConfiguration.writeToParcel(dest, flags);
+        dest.writeInt(mDisplayId);
+        dest.writeBoolean(mVisibleRequested);
+    }
+
+    private TaskFragmentParentInfo(Parcel in) {
+        mConfiguration.readFromParcel(in);
+        mDisplayId = in.readInt();
+        mVisibleRequested = in.readBoolean();
+    }
+
+    public static final Creator<TaskFragmentParentInfo> CREATOR =
+            new Creator<TaskFragmentParentInfo>() {
+                @Override
+                public TaskFragmentParentInfo createFromParcel(Parcel in) {
+                    return new TaskFragmentParentInfo(in);
+                }
+
+                @Override
+                public TaskFragmentParentInfo[] newArray(int size) {
+                    return new TaskFragmentParentInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 04fcd3a..7667743 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -173,10 +173,6 @@
         /** @see #setTaskId(int) */
         private int mTaskId;
 
-        /** @see #setTaskConfiguration(Configuration) */
-        @Nullable
-        private Configuration mTaskConfiguration;
-
         /** @see #setErrorCallbackToken(IBinder) */
         @Nullable
         private IBinder mErrorCallbackToken;
@@ -193,6 +189,9 @@
         @Nullable
         private IBinder mActivityToken;
 
+        @Nullable
+        private TaskFragmentParentInfo mTaskFragmentParentInfo;
+
         public Change(@ChangeType int type) {
             mType = type;
         }
@@ -202,11 +201,11 @@
             mTaskFragmentToken = in.readStrongBinder();
             mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
             mTaskId = in.readInt();
-            mTaskConfiguration = in.readTypedObject(Configuration.CREATOR);
             mErrorCallbackToken = in.readStrongBinder();
             mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader());
             mActivityIntent = in.readTypedObject(Intent.CREATOR);
             mActivityToken = in.readStrongBinder();
+            mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
         }
 
         @Override
@@ -215,11 +214,11 @@
             dest.writeStrongBinder(mTaskFragmentToken);
             dest.writeTypedObject(mTaskFragmentInfo, flags);
             dest.writeInt(mTaskId);
-            dest.writeTypedObject(mTaskConfiguration, flags);
             dest.writeStrongBinder(mErrorCallbackToken);
             dest.writeBundle(mErrorBundle);
             dest.writeTypedObject(mActivityIntent, flags);
             dest.writeStrongBinder(mActivityToken);
+            dest.writeTypedObject(mTaskFragmentParentInfo, flags);
         }
 
         /** The change is related to the TaskFragment created with this unique token. */
@@ -243,10 +242,10 @@
             return this;
         }
 
+        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
         /** Configuration of the parent Task. */
         @NonNull
         public Change setTaskConfiguration(@NonNull Configuration configuration) {
-            mTaskConfiguration = requireNonNull(configuration);
             return this;
         }
 
@@ -294,6 +293,19 @@
             return this;
         }
 
+        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+        /**
+         * Sets info of the parent Task of the embedded TaskFragment.
+         * @see TaskFragmentParentInfo
+         *
+         * @hide pending unhide
+         */
+        @NonNull
+        public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+            mTaskFragmentParentInfo = requireNonNull(info);
+            return this;
+        }
+
         @ChangeType
         public int getType() {
             return mType;
@@ -313,9 +325,10 @@
             return mTaskId;
         }
 
+        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
         @Nullable
         public Configuration getTaskConfiguration() {
-            return mTaskConfiguration;
+            return mTaskFragmentParentInfo.getConfiguration();
         }
 
         @Nullable
@@ -339,6 +352,13 @@
             return mActivityToken;
         }
 
+        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+        /** @hide pending unhide */
+        @Nullable
+        public TaskFragmentParentInfo getTaskFragmentParentInfo() {
+            return mTaskFragmentParentInfo;
+        }
+
         @Override
         public String toString() {
             return "Change{ type=" + mType + " }";
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 33ea2e4..fbdd325 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -125,8 +125,18 @@
     /** The container attaches work profile thumbnail for cross profile animation. */
     public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
 
+    /**
+     * Whether the window is covered by an app starting window. This is different from
+     * {@link #FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT} which is only set on the Activity window
+     * that contains the starting window.
+     */
+    public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+
+    /** This change happened underneath something else. */
+    public static final int FLAG_IS_OCCLUDED = 1 << 15;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 14;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 16;
 
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
@@ -145,6 +155,8 @@
             FLAG_WILL_IME_SHOWN,
             FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
             FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
+            FLAG_IS_BEHIND_STARTING_WINDOW,
+            FLAG_IS_OCCLUDED,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -351,6 +363,12 @@
         if ((flags & FLAG_FILLS_TASK) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
         }
+        if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
+        }
+        if ((flags & FLAG_IS_OCCLUDED) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a99c6be..2d29c59 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -694,6 +694,23 @@
     }
 
     /**
+     * Finishes the Activity.
+     * Comparing to directly calling {@link android.app.Activity#finish()}, calling this can make
+     * sure the finishing happens in the same transaction with other operations.
+     * @param activityToken activity to be finished.
+     */
+    @NonNull
+    public WindowContainerTransaction finishActivity(@NonNull IBinder activityToken) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY)
+                        .setContainer(activityToken)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * Sets/removes the always on top flag for this {@code windowContainer}. See
      * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
      * Please note that this method is only intended to be used for a
@@ -1151,6 +1168,7 @@
         public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
         public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
         public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
+        public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1472,6 +1490,8 @@
                             + " alwaysOnTop=" + mAlwaysOnTop + "}";
                 case HIERARCHY_OP_TYPE_REMOVE_TASK:
                     return "{RemoveTask: task=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
+                    return "{finishActivity: activity=" + mContainer + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 4ea5ea5..2a80d02 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -84,9 +84,8 @@
     }
 
     /**
-     * Start a transition.
+     * Starts a new transition, don't use this to start an already created one.
      * @param type The type of the transition. This is ignored if a transitionToken is provided.
-     * @param transitionToken An existing transition to start. If null, a new transition is created.
      * @param t The set of window operations that are part of this transition.
      * @return A token identifying the transition. This will be the same as transitionToken if it
      *         was provided.
@@ -94,10 +93,24 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @NonNull
-    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+    public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+        try {
+            return getWindowOrganizerController().startNewTransition(type, t);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts an already created transition.
+     * @param transitionToken An existing transition to start.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    public void startTransition(@NonNull IBinder transitionToken,
             @Nullable WindowContainerTransaction t) {
         try {
-            return getWindowOrganizerController().startTransition(type, transitionToken, t);
+            getWindowOrganizerController().startTransition(transitionToken, t);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/accessibility/OWNERS b/core/java/com/android/internal/accessibility/OWNERS
index b3c09e9..0955e00 100644
--- a/core/java/com/android/internal/accessibility/OWNERS
+++ b/core/java/com/android/internal/accessibility/OWNERS
@@ -1,4 +1,6 @@
 # Bug component: 44214
-svetoslavganov@google.com
 pweaver@google.com
-qasid@google.com
+danielnorman@google.com
+sallyyuen@google.com
+aarmaly@google.com
+fuego@google.com
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 5cf7e36..c8eec7d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1069,7 +1069,12 @@
         }
     }
 
-    private ViewGroup createContentPreviewView(ViewGroup parent) {
+    /**
+     * Create a view that will be shown in the content preview area
+     * @param parent reference to the parent container where the view should be attached to
+     * @return content preview view
+     */
+    protected ViewGroup createContentPreviewView(ViewGroup parent) {
         Intent targetIntent = getTargetIntent();
         int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
         return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
@@ -2653,7 +2658,7 @@
 
             boolean isExpandable = getResources().getConfiguration().orientation
                     == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
-            if (directShareHeight != 0 && isSendAction(getTargetIntent())
+            if (directShareHeight != 0 && shouldShowContentPreview()
                     && isExpandable) {
                 // make sure to leave room for direct share 4->8 expansion
                 int requiredExpansionHeight =
@@ -2901,7 +2906,14 @@
         return shouldShowTabs()
                 && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
                 UserHandle.of(UserHandle.myUserId())).getCount() > 0
-                && isSendAction(getTargetIntent());
+                && shouldShowContentPreview();
+    }
+
+    /**
+     * @return true if we want to show the content preview area
+     */
+    protected boolean shouldShowContentPreview() {
+        return isSendAction(getTargetIntent());
     }
 
     private void updateStickyContentPreview() {
@@ -3234,7 +3246,7 @@
                 return 0;
             }
 
-            if (!isSendAction(getTargetIntent())) {
+            if (!shouldShowContentPreview()) {
                 return 0;
             }
 
@@ -3265,7 +3277,7 @@
         // There can be at most one row in the listview, that is internally
         // a ViewGroup with 2 rows
         public int getServiceTargetRowCount() {
-            if (isSendAction(getTargetIntent())
+            if (shouldShowContentPreview()
                     && !ActivityManager.isLowRamDeviceStatic()) {
                 return 1;
             }
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1ec5325..4f74ca7 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -86,7 +86,6 @@
     private final ChooserActivityLogger mChooserActivityLogger;
 
     private int mNumShortcutResults = 0;
-    private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
     private boolean mApplySharingAppLimits;
 
     // Reserve spots for incoming direct share targets by adding placeholders
@@ -265,31 +264,20 @@
             return;
         }
 
-        if (!(info instanceof DisplayResolveInfo)) {
-            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
-            holder.bindIcon(info);
-
-            if (info instanceof SelectableTargetInfo) {
-                // direct share targets should append the application name for a better readout
-                DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
-                CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
-                CharSequence extendedInfo = info.getExtendedInfo();
-                String contentDescription = String.join(" ", info.getDisplayLabel(),
-                        extendedInfo != null ? extendedInfo : "", appName);
-                holder.updateContentDescription(contentDescription);
-            }
-        } else {
+        holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
+        holder.bindIcon(info);
+        if (info instanceof SelectableTargetInfo) {
+            // direct share targets should append the application name for a better readout
+            DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
+            CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+            CharSequence extendedInfo = info.getExtendedInfo();
+            String contentDescription = String.join(" ", info.getDisplayLabel(),
+                    extendedInfo != null ? extendedInfo : "", appName);
+            holder.updateContentDescription(contentDescription);
+        } else if (info instanceof DisplayResolveInfo) {
             DisplayResolveInfo dri = (DisplayResolveInfo) info;
-            holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel());
-            LoadIconTask task = mIconLoaders.get(dri);
-            if (task == null) {
-                task = new LoadIconTask(dri, holder);
-                mIconLoaders.put(dri, task);
-                task.execute();
-            } else {
-                // The holder was potentially changed as the underlying items were
-                // reshuffled, so reset the target holder
-                task.setViewHolder(holder);
+            if (!dri.hasDisplayIcon()) {
+                loadIcon(dri);
             }
         }
 
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 3732ea5..787b594 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -84,6 +84,11 @@
     @RequiresNoPermission
     long computeChargeTimeRemaining();
 
+    @EnforcePermission("BATTERY_STATS")
+    long computeBatteryScreenOffRealtimeMs();
+    @EnforcePermission("BATTERY_STATS")
+    long getScreenOffDischargeMah();
+
     @EnforcePermission("UPDATE_DEVICE_STATS")
     void noteEvent(int code, String name, int uid);
 
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl
similarity index 67%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to core/java/com/android/internal/app/ILogAccessDialogCallback.aidl
index 9b370d8..b2236c9 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/core/java/com/android/internal/app/ILogAccessDialogCallback.aidl
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package com.android.internal.app;
 
 /**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * IPC interface for an application to receive callbacks from the log access dialog callback.
  */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
-}
+oneway interface ILogAccessDialogCallback {
+    void approveAccessForClient(int uid, String packageName);
+    void declineAccessForClient(int uid, String packageName);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 8d51c9c..bbcf982 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
 import android.os.Bundle;
@@ -25,18 +27,17 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VisibleActivityInfo;
 
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.internal.app.IVoiceInteractionSessionShowCallback;
-import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.service.voice.IVoiceInteractionService;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import com.android.internal.app.IVoiceInteractor;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -303,4 +304,14 @@
      * Notifies when the session window is shown or hidden.
      */
     void setSessionWindowVisible(in IBinder token, boolean visible);
+
+    /**
+     * Notifies when the Activity lifecycle event changed.
+     *
+     * @param activityToken The token of activity.
+     * @param type The type of lifecycle event of the activity lifecycle.
+     */
+    oneway void notifyActivityEventChanged(
+            in IBinder activityToken,
+            int type);
 }
diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/core/java/com/android/internal/app/LogAccessDialogActivity.java
similarity index 71%
rename from services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
rename to core/java/com/android/internal/app/LogAccessDialogActivity.java
index 811e96c..4adb867 100644
--- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
+++ b/core/java/com/android/internal/app/LogAccessDialogActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.logcat;
+package com.android.internal.app;
 
 import android.annotation.StyleRes;
 import android.app.Activity;
@@ -27,7 +27,14 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
 import android.view.InflateException;
@@ -37,7 +44,6 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.server.LocalServices;
 
 /**
  * Dialog responsible for obtaining user consent per-use log access
@@ -45,17 +51,19 @@
 public class LogAccessDialogActivity extends Activity implements
         View.OnClickListener {
     private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
+    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
+
 
     private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
     private static final int MSG_DISMISS_DIALOG = 0;
 
-    private final LogcatManagerService.LogcatManagerServiceInternal mLogcatManagerInternal =
-            LocalServices.getService(LogcatManagerService.LogcatManagerServiceInternal.class);
-
     private String mPackageName;
     private int mUid;
+    private ILogAccessDialogCallback mCallback;
 
     private String mAlertTitle;
+    private String mAlertBody;
+    private String mAlertLearnMore;
     private AlertDialog.Builder mAlertDialog;
     private AlertDialog mAlert;
     private View mAlertView;
@@ -81,6 +89,9 @@
             return;
         }
 
+        mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
+        mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
+
         // create View
         boolean isDarkTheme = (getResources().getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
@@ -118,6 +129,13 @@
             return false;
         }
 
+        mCallback = ILogAccessDialogCallback.Stub.asInterface(
+                intent.getExtras().getBinder(EXTRA_CALLBACK));
+        if (mCallback == null) {
+            Slog.e(TAG, "Missing callback");
+            return false;
+        }
+
         mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
         if (mPackageName == null || mPackageName.length() == 0) {
             Slog.e(TAG, "Missing package name extra");
@@ -165,13 +183,22 @@
         return titleString;
     }
 
+    private Spannable styleFont(String text) {
+        Spannable s = (Spannable) Html.fromHtml(text);
+        for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
+            TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
+            s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
+        }
+        return s;
+    }
+
     /**
      * Returns the dialog view.
      * If we cannot retrieve the package name, it returns null and we decline the full device log
      * access
      */
     private View createView(@StyleRes int themeId) {
-        Context themedContext = new ContextThemeWrapper(getApplicationContext(), themeId);
+        Context themedContext = new ContextThemeWrapper(this, themeId);
         final View view = LayoutInflater.from(themedContext).inflate(
                 R.layout.log_access_user_consent_dialog_permission, null /*root*/);
 
@@ -182,6 +209,19 @@
         ((TextView) view.findViewById(R.id.log_access_dialog_title))
             .setText(mAlertTitle);
 
+        if (!TextUtils.isEmpty(mAlertLearnMore)) {
+            Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
+
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
+
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setMovementMethod(LinkMovementMethod.getInstance());
+        } else {
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setText(mAlertBody);
+        }
+
         Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
         button_allow.setOnClickListener(this);
 
@@ -194,19 +234,27 @@
 
     @Override
     public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.log_access_dialog_allow_button:
-                mLogcatManagerInternal.approveAccessForClient(mUid, mPackageName);
-                finish();
-                break;
-            case R.id.log_access_dialog_deny_button:
-                declineLogAccess();
-                finish();
-                break;
+        try {
+            switch (view.getId()) {
+                case R.id.log_access_dialog_allow_button:
+                    mCallback.approveAccessForClient(mUid, mPackageName);
+                    finish();
+                    break;
+                case R.id.log_access_dialog_deny_button:
+                    declineLogAccess();
+                    finish();
+                    break;
+            }
+        } catch (RemoteException e) {
+            finish();
         }
     }
 
     private void declineLogAccess() {
-        mLogcatManagerInternal.declineAccessForClient(mUid, mPackageName);
+        try {
+            mCallback.declineAccessForClient(mUid, mPackageName);
+        } catch (RemoteException e) {
+            finish();
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index c8bc204..822393f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -55,6 +55,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -1475,14 +1476,21 @@
                 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
         boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
 
-        ResolverListAdapter inactiveAdapter = mMultiProfilePagerAdapter.getInactiveListAdapter();
-        DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
+        final ResolverListAdapter inactiveAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
 
         // Load the icon asynchronously
         ImageView icon = findViewById(R.id.icon);
-        ResolverListAdapter.LoadIconTask iconTask = inactiveAdapter.new LoadIconTask(
-                        otherProfileResolveInfo, new ResolverListAdapter.ViewHolder(icon));
-        iconTask.execute();
+        inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) {
+            @Override
+            protected void onPostExecute(Drawable drawable) {
+                if (!isDestroyed()) {
+                    otherProfileResolveInfo.setDisplayIcon(drawable);
+                    new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
+                }
+            }
+        }.execute();
 
         ((TextView) findViewById(R.id.open_cross_profile)).setText(
                 getResources().getString(
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 66fff5c..f6075b0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -58,7 +58,10 @@
 import com.android.internal.app.chooser.TargetInfo;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class ResolverListAdapter extends BaseAdapter {
     private static final String TAG = "ResolverListAdapter";
@@ -87,6 +90,8 @@
     private Runnable mPostListReadyRunnable;
     private final boolean mIsAudioCaptureDevice;
     private boolean mIsTabLoaded;
+    private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+    private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>();
 
     public ResolverListAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList,
@@ -636,26 +641,47 @@
         if (info == null) {
             holder.icon.setImageDrawable(
                     mContext.getDrawable(R.drawable.resolver_icon_placeholder));
+            holder.bindLabel("", "", false);
             return;
         }
 
-        if (info instanceof DisplayResolveInfo
-                && !((DisplayResolveInfo) info).hasDisplayLabel()) {
-            getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
-        } else {
-            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
-        }
-
-        if (info instanceof DisplayResolveInfo
-                && !((DisplayResolveInfo) info).hasDisplayIcon()) {
-            new LoadIconTask((DisplayResolveInfo) info, holder).execute();
-        } else {
+        if (info instanceof DisplayResolveInfo) {
+            DisplayResolveInfo dri = (DisplayResolveInfo) info;
+            boolean hasLabel = dri.hasDisplayLabel();
+            holder.bindLabel(
+                    dri.getDisplayLabel(),
+                    dri.getExtendedInfo(),
+                    hasLabel && alwaysShowSubLabel());
             holder.bindIcon(info);
+            if (!hasLabel) {
+                loadLabel(dri);
+            }
+            if (!dri.hasDisplayIcon()) {
+                loadIcon(dri);
+            }
         }
     }
 
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelTask(info, holder);
+    protected final void loadIcon(DisplayResolveInfo info) {
+        LoadIconTask task = mIconLoaders.get(info);
+        if (task == null) {
+            task = new LoadIconTask((DisplayResolveInfo) info);
+            mIconLoaders.put(info, task);
+            task.execute();
+        }
+    }
+
+    private void loadLabel(DisplayResolveInfo info) {
+        LoadLabelTask task = mLabelLoaders.get(info);
+        if (task == null) {
+            task = createLoadLabelTask(info);
+            mLabelLoaders.put(info, task);
+            task.execute();
+        }
+    }
+
+    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+        return new LoadLabelTask(info);
     }
 
     public void onDestroy() {
@@ -666,6 +692,16 @@
         if (mResolverListController != null) {
             mResolverListController.destroy();
         }
+        cancelTasks(mIconLoaders.values());
+        cancelTasks(mLabelLoaders.values());
+        mIconLoaders.clear();
+        mLabelLoaders.clear();
+    }
+
+    private <T extends AsyncTask> void cancelTasks(Collection<T> tasks) {
+        for (T task: tasks) {
+            task.cancel(false);
+        }
     }
 
     private static ColorMatrixColorFilter getSuspendedColorMatrix() {
@@ -883,11 +919,9 @@
 
     protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
         private final DisplayResolveInfo mDisplayResolveInfo;
-        private final ViewHolder mHolder;
 
-        protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
+        protected LoadLabelTask(DisplayResolveInfo dri) {
             mDisplayResolveInfo = dri;
-            mHolder = holder;
         }
 
         @Override
@@ -925,21 +959,22 @@
 
         @Override
         protected void onPostExecute(CharSequence[] result) {
+            if (mDisplayResolveInfo.hasDisplayLabel()) {
+                return;
+            }
             mDisplayResolveInfo.setDisplayLabel(result[0]);
             mDisplayResolveInfo.setExtendedInfo(result[1]);
-            mHolder.bindLabel(result[0], result[1], alwaysShowSubLabel());
+            notifyDataSetChanged();
         }
     }
 
     class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
         protected final DisplayResolveInfo mDisplayResolveInfo;
         private final ResolveInfo mResolveInfo;
-        private ViewHolder mHolder;
 
-        LoadIconTask(DisplayResolveInfo dri, ViewHolder holder) {
+        LoadIconTask(DisplayResolveInfo dri) {
             mDisplayResolveInfo = dri;
             mResolveInfo = dri.getResolveInfo();
-            mHolder = holder;
         }
 
         @Override
@@ -953,17 +988,9 @@
                 mResolverListCommunicator.updateProfileViewButton();
             } else if (!mDisplayResolveInfo.hasDisplayIcon()) {
                 mDisplayResolveInfo.setDisplayIcon(d);
-                mHolder.bindIcon(mDisplayResolveInfo);
-                // Notify in case view is already bound to resolve the race conditions on
-                // low end devices
                 notifyDataSetChanged();
             }
         }
-
-        public void setViewHolder(ViewHolder holder) {
-            mHolder = holder;
-            mHolder.bindIcon(mDisplayResolveInfo);
-        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index 354eb62..be0b729 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -51,6 +51,7 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -69,6 +70,7 @@
 
     private static final SynchronizedPool<SimpleIconFactory> sPool =
             new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
+    private static boolean sPoolEnabled = true;
 
     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
     private static final float BLUR_FACTOR = 1.5f / 48;
@@ -92,7 +94,7 @@
      */
     @Deprecated
     public static SimpleIconFactory obtain(Context ctx) {
-        SimpleIconFactory instance = sPool.acquire();
+        SimpleIconFactory instance = sPoolEnabled ? sPool.acquire() : null;
         if (instance == null) {
             final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE);
             final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity();
@@ -106,6 +108,17 @@
         return instance;
     }
 
+    /**
+     * Enables or disables SimpleIconFactory objects pooling. It is enabled in production, you
+     * could use this method in tests and disable the pooling to make the icon rendering more
+     * deterministic because some sizing parameters will not be cached. Please ensure that you
+     * reset this value back after finishing the test.
+     */
+    @VisibleForTesting
+    public static void setPoolEnabled(boolean poolEnabled) {
+        sPoolEnabled = poolEnabled;
+    }
+
     private static int getAttrDimFromContext(Context ctx, @AttrRes int attrId, String errorMsg) {
         final Resources res = ctx.getResources();
         TypedValue outVal = new TypedValue();
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 87e8ac1..72b9cd2 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -473,7 +473,10 @@
                 }
             }
             mCurCombinedState = state;
-            mStats.mUidStates.get(mUid).updateCombinedState(state, now);
+            final UidState uidState = mStats.mUidStates.get(mUid);
+            if (uidState != null) {
+                uidState.updateCombinedState(state, now);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a..d503904 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@
             Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
         }
 
-        mBrightnessSyncObserver.startObserving();
+        mBrightnessSyncObserver.startObserving(mHandler);
         mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
     }
 
@@ -482,30 +482,31 @@
             }
         };
 
-        private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                if (selfChange) {
-                    return;
+        private ContentObserver createBrightnessContentObserver(Handler handler) {
+            return new ContentObserver(handler) {
+                @Override
+                public void onChange(boolean selfChange, Uri uri) {
+                    if (selfChange) {
+                        return;
+                    }
+                    if (BRIGHTNESS_URI.equals(uri)) {
+                        handleBrightnessChangeInt(getScreenBrightnessInt());
+                    }
                 }
-                if (BRIGHTNESS_URI.equals(uri)) {
-                    handleBrightnessChangeInt(getScreenBrightnessInt());
-                }
-            }
-        };
+            };
+        }
 
         boolean isObserving() {
             return mIsObserving;
         }
 
-        void startObserving() {
+        void startObserving(Handler handler) {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
-                    UserHandle.USER_ALL);
-            mDisplayManager.registerDisplayListener(mListener, mHandler,
+            cr.registerContentObserver(BRIGHTNESS_URI, false,
+                    createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+            mDisplayManager.registerDisplayListener(mListener, handler,
                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
             mIsObserving = true;
         }
-
     }
 }
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 1e52087..f260d7d 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -41,6 +41,8 @@
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.widget.TextView;
 
@@ -277,6 +279,10 @@
             result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture);
         } else if (gesture instanceof InsertGesture) {
             result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture);
+        } else if (gesture instanceof RemoveSpaceGesture) {
+            result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture);
+        } else if (gesture instanceof JoinOrSplitGesture) {
+            result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture);
         } else {
             result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED;
         }
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index 17f9b7d..ea5c9a3 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -137,4 +137,7 @@
             int afterLength, int flags, in AndroidFuture future /* T=SurroundingText */);
 
     void setImeConsumesInput(in InputConnectionCommandHeader header, boolean imeConsumesInput);
+
+    void replaceText(in InputConnectionCommandHeader header, int start, int end, CharSequence text,
+            int newCursorPosition,in TextAttribute textAttribute);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java b/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java
index 5912177..a82c6dc 100644
--- a/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java
+++ b/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java
@@ -21,7 +21,7 @@
 import android.os.Parcelable;
 
 /**
- * A common IPC header used behind {@link RemoteInputConnectionImpl} and
+ * A common IPC header used behind {@link android.view.inputmethod.RemoteInputConnectionImpl} and
  * {@link android.inputmethodservice.RemoteInputConnection}.
  */
 public final class InputConnectionCommandHeader implements Parcelable {
diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
new file mode 100644
index 0000000..6139bce
--- /dev/null
+++ b/core/java/com/android/internal/jank/EventLogTags.logtags
@@ -0,0 +1,10 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.internal.jank;
+
+# Marks a request to start tracing a CUJ. Doesn't mean the request was executed.
+37001 jank_cuj_events_begin_request (CUJ Type|1|5)
+# Marks a request to end tracing a CUJ. Doesn't mean the request was executed.
+37002 jank_cuj_events_end_request (CUJ Type|1|5)
+# Marks a request to cancel tracing a CUJ. Doesn't mean the request was executed.
+37003 jank_cuj_events_cancel_request (CUJ Type|1|5)
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index b5991b3..76f33a6 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -29,7 +29,9 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
@@ -220,6 +222,8 @@
     public static final int CUJ_TASKBAR_EXPAND = 60;
     public static final int CUJ_TASKBAR_COLLAPSE = 61;
     public static final int CUJ_SHADE_CLEAR_ALL = 62;
+    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
+    public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -291,6 +295,8 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
     };
 
     private static class InstanceHolder {
@@ -377,7 +383,9 @@
             CUJ_USER_DIALOG_OPEN,
             CUJ_TASKBAR_EXPAND,
             CUJ_TASKBAR_COLLAPSE,
-            CUJ_SHADE_CLEAR_ALL
+            CUJ_SHADE_CLEAR_ALL,
+            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+            CUJ_LOCKSCREEN_OCCLUSION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -434,6 +442,22 @@
     @VisibleForTesting
     public FrameTracker createFrameTracker(Configuration config, Session session) {
         final View view = config.mView;
+
+        if (!config.hasValidView()) {
+            boolean attached = false;
+            boolean hasViewRoot = false;
+            boolean hasRenderer = false;
+            if (view != null) {
+                attached = view.isAttachedToWindow();
+                hasViewRoot = view.getViewRootImpl() != null;
+                hasRenderer = view.getThreadedRenderer() != null;
+            }
+            Log.d(TAG, "create FrameTracker fails: view=" + view
+                    + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot
+                    + ", hasRenderer=" + hasRenderer, new Throwable());
+            return null;
+        }
+
         final ThreadedRendererWrapper threadedRenderer =
                 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
         final ViewRootWrapper viewRoot =
@@ -517,6 +541,7 @@
     public boolean begin(@NonNull Configuration.Builder builder) {
         try {
             final Configuration config = builder.build();
+            EventLogTags.writeJankCujEventsBeginRequest(config.mCujType);
             final TrackerResult result = new TrackerResult();
             final boolean success = config.getHandler().runWithScissors(
                     () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
@@ -541,6 +566,7 @@
 
         // begin a new trace session.
         tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
+        if (tracker == null) return false;
         putTracker(cujType, tracker);
         tracker.begin();
 
@@ -589,6 +615,7 @@
      * @return boolean true if the tracker is ended successfully, false otherwise.
      */
     public boolean end(@CujType int cujType) {
+        EventLogTags.writeJankCujEventsEndRequest(cujType);
         FrameTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
         if (tracker == null) return false;
@@ -626,6 +653,7 @@
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
     public boolean cancel(@CujType int cujType) {
+        EventLogTags.writeJankCujEventsCancelRequest(cujType);
         return cancel(cujType, REASON_CANCEL_NORMAL);
     }
 
@@ -868,6 +896,10 @@
                 return "TASKBAR_COLLAPSE";
             case CUJ_SHADE_CLEAR_ALL:
                 return "SHADE_CLEAR_ALL";
+            case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
+                return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
+            case CUJ_LOCKSCREEN_OCCLUSION:
+                return "LOCKSCREEN_OCCLUSION";
         }
         return "UNKNOWN";
     }
@@ -1058,9 +1090,19 @@
                     msg.append("Must pass in a valid surface control if only instrument surface; ");
                 }
             } else {
-                if (mView == null || !mView.isAttachedToWindow()) {
+                if (!hasValidView()) {
                     shouldThrow = true;
-                    msg.append("Null view or unattached view while instrumenting view; ");
+                    boolean attached = false;
+                    boolean hasViewRoot = false;
+                    boolean hasRenderer = false;
+                    if (mView != null) {
+                        attached = mView.isAttachedToWindow();
+                        hasViewRoot = mView.getViewRootImpl() != null;
+                        hasRenderer = mView.getThreadedRenderer() != null;
+                    }
+                    String err = "invalid view: view=" + mView + ", attached=" + attached
+                            + ", hasViewRoot=" + hasViewRoot + ", hasRenderer=" + hasRenderer;
+                    msg.append(err);
                 }
             }
             if (shouldThrow) {
@@ -1068,6 +1110,12 @@
             }
         }
 
+        boolean hasValidView() {
+            return mSurfaceOnly
+                    || (mView != null && mView.isAttachedToWindow()
+                    && mView.getViewRootImpl() != null && mView.getThreadedRenderer() != null);
+        }
+
         /**
          * @return true if only instrumenting surface, false otherwise
          */
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 5523344..696f0ff 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.BitDescription;
+import android.os.BatteryStats.CpuUsageDetails;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.HistoryStepDetails;
 import android.os.BatteryStats.HistoryTag;
@@ -78,7 +79,7 @@
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    private static final int VERSION = 208;
+    private static final int VERSION = 209;
 
     private static final String HISTORY_DIR = "battery-history";
     private static final String FILE_SUFFIX = ".bin";
@@ -122,6 +123,8 @@
 
     static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
     static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
+    static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
+    static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
 
     private final Parcel mHistoryBuffer;
     private final File mSystemDir;
@@ -194,6 +197,8 @@
     private long mTrackRunningHistoryUptimeMs = 0;
     private long mHistoryBaseTimeMs;
     private boolean mMeasuredEnergyHeaderWritten = false;
+    private boolean mCpuUsageHeaderWritten = false;
+    private final VarintParceler mVarintParceler = new VarintParceler();
 
     private byte mLastHistoryStepLevel = 0;
 
@@ -351,6 +356,7 @@
         mTrackRunningHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryUptimeMs = 0;
         mMeasuredEnergyHeaderWritten = false;
+        mCpuUsageHeaderWritten = false;
 
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
@@ -1180,7 +1186,7 @@
 
         final int idx = code & HistoryItem.EVENT_TYPE_MASK;
         final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
-                  (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
+                (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
 
         final String[] names = BatteryStats.HISTORY_EVENT_NAMES;
         if (idx < 0 || idx >= names.length) return;
@@ -1191,6 +1197,17 @@
     }
 
     /**
+     * Records CPU usage by a specific UID.  The recorded data is the delta from
+     * the previous record for the same UID.
+     */
+    public void recordCpuUsage(long elapsedRealtimeMs, long uptimeMs,
+            CpuUsageDetails cpuUsageDetails) {
+        mHistoryCur.cpuUsageDetails = cpuUsageDetails;
+        mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
      * Writes changes to a HistoryItem state bitmap to Atrace.
      */
     private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
@@ -1338,6 +1355,7 @@
                 entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
             }
             mMeasuredEnergyHeaderWritten = false;
+            mCpuUsageHeaderWritten = false;
 
             // Make a copy of mHistoryCur.
             HistoryItem copy = new HistoryItem();
@@ -1377,6 +1395,7 @@
         cur.eventTag = null;
         cur.tagsFirstOccurrence = false;
         cur.measuredEnergyDetails = null;
+        cur.cpuUsageDetails = null;
         if (DEBUG) {
             Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
                     + " now " + mHistoryBuffer.dataPosition()
@@ -1502,12 +1521,18 @@
                 extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
             }
         }
+        if (cur.cpuUsageDetails != null) {
+            extensionFlags |= EXTENSION_CPU_USAGE_FLAG;
+            if (!mCpuUsageHeaderWritten) {
+                extensionFlags |= BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG;
+            }
+        }
         if (extensionFlags != 0) {
             cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
         } else {
             cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG;
         }
-        final boolean state2IntChanged = cur.states2 != last.states2;
+        final boolean state2IntChanged = cur.states2 != last.states2 || extensionFlags != 0;
         if (state2IntChanged) {
             firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
         }
@@ -1641,9 +1666,19 @@
                     }
                     mMeasuredEnergyHeaderWritten = true;
                 }
-                for (long chargeUC : cur.measuredEnergyDetails.chargeUC) {
-                    dest.writeLong(chargeUC);
+                mVarintParceler.writeLongArray(dest, cur.measuredEnergyDetails.chargeUC);
+            }
+
+            if (cur.cpuUsageDetails != null) {
+                if (DEBUG) {
+                    Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
                 }
+                if (!mCpuUsageHeaderWritten) {
+                    dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+                    mCpuUsageHeaderWritten = true;
+                }
+                dest.writeInt(cur.cpuUsageDetails.uid);
+                mVarintParceler.writeLongArray(dest, cur.cpuUsageDetails.cpuUsageMs);
             }
         }
     }
@@ -1892,4 +1927,74 @@
                     entry.getKey());
         }
     }
+
+    /**
+     * Writes/reads an array of longs into Parcel using a compact format, where small integers use
+     * fewer bytes.  It is a bit more expensive than just writing the long into the parcel,
+     * but at scale saves a lot of storage and allows recording of longer battery history.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final class VarintParceler {
+        /**
+         * Writes an array of longs into Parcel using the varint format, see
+         * https://developers.google.com/protocol-buffers/docs/encoding#varints
+         */
+        public void writeLongArray(Parcel parcel, long[] values) {
+            int out = 0;
+            int shift = 0;
+            for (long value : values) {
+                boolean done = false;
+                while (!done) {
+                    final byte b;
+                    if ((value & ~0x7FL) == 0) {
+                        b = (byte) value;
+                        done = true;
+                    } else {
+                        b = (byte) (((int) value & 0x7F) | 0x80);
+                        value >>>= 7;
+                    }
+                    if (shift == 32) {
+                        parcel.writeInt(out);
+                        shift = 0;
+                        out = 0;
+                    }
+                    out |= (b & 0xFF) << shift;
+                    shift += 8;
+                }
+            }
+            if (shift != 0) {
+                parcel.writeInt(out);
+            }
+        }
+
+        /**
+         * Reads a long written with {@link #writeLongArray}
+         */
+        public void readLongArray(Parcel parcel, long[] values) {
+            int in = parcel.readInt();
+            int available = 4;
+            for (int i = 0; i < values.length; i++) {
+                long result = 0;
+                int shift;
+                for (shift = 0; shift < 64; shift += 7) {
+                    if (available == 0) {
+                        in = parcel.readInt();
+                        available = 4;
+                    }
+                    final byte b = (byte) in;
+                    in >>= 8;
+                    available--;
+
+                    result |= (long) (b & 0x7F) << shift;
+                    if ((b & 0x80) == 0) {
+                        values[i] = result;
+                        break;
+                    }
+                }
+                if (shift >= 64) {
+                    throw new ParcelFormatException("Invalid varint format");
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index ee3d15b..09fe100 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -34,6 +34,9 @@
             new BatteryStats.HistoryStepDetails();
     private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
     private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails;
+    private BatteryStats.CpuUsageDetails mCpuUsageDetails;
+    private final BatteryStatsHistory.VarintParceler mVarintParceler =
+            new BatteryStatsHistory.VarintParceler();
 
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
@@ -60,7 +63,7 @@
         return true;
     }
 
-    void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
+    private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
         int firstToken = src.readInt();
         int deltaTimeToken = firstToken & BatteryStatsHistory.DELTA_TIME_MASK;
         cur.cmd = BatteryStats.HistoryItem.CMD_UPDATE;
@@ -225,13 +228,35 @@
                     throw new IllegalStateException("MeasuredEnergyDetails without a header");
                 }
 
-                for (int i = 0; i < mMeasuredEnergyDetails.chargeUC.length; i++) {
-                    mMeasuredEnergyDetails.chargeUC[i] = src.readLong();
-                }
+                mVarintParceler.readLongArray(src, mMeasuredEnergyDetails.chargeUC);
                 cur.measuredEnergyDetails = mMeasuredEnergyDetails;
+            } else {
+                cur.measuredEnergyDetails = null;
+            }
+
+            if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
+                mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
+                mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+                mCpuUsageDetails.cpuUsageMs =
+                        new long[mCpuUsageDetails.cpuBracketDescriptions.length];
+            } else if (mCpuUsageDetails != null) {
+                mCpuUsageDetails.cpuBracketDescriptions = null;
+            }
+
+            if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_FLAG) != 0) {
+                if (mCpuUsageDetails == null) {
+                    throw new IllegalStateException("CpuUsageDetails without a header");
+                }
+
+                mCpuUsageDetails.uid = src.readInt();
+                mVarintParceler.readLongArray(src, mCpuUsageDetails.cpuUsageMs);
+                cur.cpuUsageDetails = mCpuUsageDetails;
+            } else {
+                cur.cpuUsageDetails = null;
             }
         } else {
             cur.measuredEnergyDetails = null;
+            cur.cpuUsageDetails = null;
         }
     }
 
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 07a8998..de3edeb 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -33,7 +33,6 @@
 import java.nio.file.Paths;
 import java.util.Arrays;
 
-@VisibleForTesting(visibility = PACKAGE)
 public class KernelSingleUidTimeReader {
     private static final String TAG = KernelSingleUidTimeReader.class.getName();
     private static final boolean DBG = false;
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 7058341..0df006d 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -119,6 +119,14 @@
     private final String[] mProcessFullStatsStringData = new String[6];
     private final long[] mProcessFullStatsData = new long[6];
 
+    private static final int[] PROCESS_SCHEDSTATS_FORMAT = new int[] {
+            PROC_SPACE_TERM|PROC_OUT_LONG,
+            PROC_SPACE_TERM|PROC_OUT_LONG,
+    };
+
+    static final int PROCESS_SCHEDSTAT_CPU_TIME = 0;
+    static final int PROCESS_SCHEDSTAT_CPU_DELAY_TIME = 1;
+
     private static final int[] SYSTEM_CPU_FORMAT = new int[] {
         PROC_SPACE_TERM|PROC_COMBINE,
         PROC_SPACE_TERM|PROC_OUT_LONG,                  // 1: user time
@@ -617,8 +625,8 @@
     }
 
     /**
-     * Returns the total time (in milliseconds) spent executing in
-     * both user and system code.  Safe to call without lock held.
+     * Returns the total time (in milliseconds) the given PID has spent
+     * executing in both user and system code. Safe to call without lock held.
      */
     public long getCpuTimeForPid(int pid) {
         synchronized (mSinglePidStatsData) {
@@ -635,6 +643,22 @@
     }
 
     /**
+     * Returns the total time (in milliseconds) the given PID has spent waiting
+     * in the runqueue. Safe to call without lock held.
+     */
+    public long getCpuDelayTimeForPid(int pid) {
+        synchronized (mSinglePidStatsData) {
+            final String statFile = "/proc/" + pid + "/schedstat";
+            final long[] statsData = mSinglePidStatsData;
+            if (Process.readProcFile(statFile, PROCESS_SCHEDSTATS_FORMAT,
+                    null, statsData, null)) {
+                return statsData[PROCESS_SCHEDSTAT_CPU_DELAY_TIME] / 1_000_000;
+            }
+            return 0;
+        }
+    }
+
+    /**
      * @return time in milliseconds.
      */
     final public int getLastUserTime() {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b1e7d15..deafd19 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -1001,16 +1001,24 @@
     }
 
     /**
+     * This will enable jdwp by default for all apps. It is OK to cache this property
+     * because we expect to reboot the system whenever this property changes
+     */
+    private static final boolean ENABLE_JDWP = SystemProperties.get(
+                          "persist.debug.dalvik.vm.jdwp.enabled").equals("1");
+
+    /**
      * Applies debugger system properties to the zygote arguments.
      *
-     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-jdwp" flag
-     * in the spawn request.
+     * For eng builds all apps are debuggable. On userdebug and user builds
+     * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are
+     * debuggable. Otherwise, the debugger state is specified via the
+     * "--enable-jdwp" flag in the spawn request.
      *
      * @param args non-null; zygote spawner args
      */
     static void applyDebuggerSystemProperty(ZygoteArguments args) {
-        if (RoSystemProperties.DEBUGGABLE) {
+        if (Build.IS_ENG || ENABLE_JDWP) {
             args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
         }
     }
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 0e8dc07..8f943ef 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -88,6 +88,7 @@
     WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             "CoreBackPreview"),
+    WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 76f7b21..cb5820f 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -90,8 +90,15 @@
         return (retval == 1);
     }
 
-    /** Returns hash of a root node for the fs-verity enabled file. */
-    public static byte[] getFsverityRootHash(@NonNull String filePath) {
+    /**
+     * Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a
+     * hash of root hash of fs-verity's Merkle tree with extra metadata.
+     *
+     * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#file-digest-computation">
+     *      File digest computation in Linux kernel documentation</a>
+     * @return Bytes of fs-verity digest
+     */
+    public static byte[] getFsverityDigest(@NonNull String filePath) {
         byte[] result = new byte[HASH_SIZE_BYTES];
         int retval = measureFsverityNative(filePath, result);
         if (retval < 0) {
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index d3f9e0a..8fcb6d5 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -29,6 +29,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS;
@@ -174,6 +175,12 @@
      */
     public static final int ACTION_FOLD_TO_AOD = 18;
 
+    /**
+     * Time it takes to show the {@link android.service.voice.VoiceInteractionSession} system UI
+     * after a {@link android.hardware.soundtrigger3.ISoundTriggerHw} voice trigger.
+     */
+    public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -194,6 +201,7 @@
         ACTION_LOAD_SHARE_SHEET,
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
+        ACTION_SHOW_VOICE_INTERACTION,
     };
 
     /** @hide */
@@ -217,6 +225,7 @@
         ACTION_LOAD_SHARE_SHEET,
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
+        ACTION_SHOW_VOICE_INTERACTION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -243,6 +252,7 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -340,6 +350,8 @@
                 return "ACTION_SHOW_SELECTION_TOOLBAR";
             case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD:
                 return "ACTION_FOLD_TO_AOD";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
+                return "ACTION_SHOW_VOICE_INTERACTION";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 9474f6f..79c5196 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -377,6 +377,9 @@
             msg.replyTo = new Messenger(h);
 
             if (mScreenshotConnection == null || mScreenshotService == null) {
+                if (mScreenshotConnection != null) {
+                    resetConnection();
+                }
                 final ComponentName serviceComponent = ComponentName.unflattenFromString(
                         mContext.getResources().getString(
                                 com.android.internal.R.string.config_screenshotServiceComponent));
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index f073c1c0..2bfde24 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -22,36 +22,24 @@
 import android.os.Message;
 
 import com.android.internal.util.function.DecConsumer;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.DodecConsumer;
-import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HeptConsumer;
-import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexConsumer;
-import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.NonaConsumer;
-import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.OctConsumer;
-import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadConsumer;
-import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuadPredicate;
 import com.android.internal.util.function.QuintConsumer;
-import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.QuintPredicate;
 import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.TriPredicate;
 import com.android.internal.util.function.UndecConsumer;
-import com.android.internal.util.function.UndecFunction;
 import com.android.internal.util.function.pooled.PooledLambdaImpl.LambdaType.ReturnType;
 
 import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 /**
@@ -194,40 +182,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1) }
-     */
-    static <A> PooledSupplier<Boolean> obtainSupplier(
-            Predicate<? super A> function,
-            A arg1) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 1, 0, ReturnType.BOOLEAN, arg1, null, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1) }
-     */
-    static <A, R> PooledSupplier<R> obtainSupplier(
-            Function<? super A, ? extends R> function,
-            A arg1) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 1, 0, ReturnType.OBJECT, arg1, null, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -279,42 +233,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2) }
-     */
-    static <A, B> PooledSupplier<Boolean> obtainSupplier(
-            BiPredicate<? super A, ? super B> function,
-            A arg1, B arg2) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 2, 0, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2) }
-     */
-    static <A, B, R> PooledSupplier<R> obtainSupplier(
-            BiFunction<? super A, ? super B, ? extends R> function,
-            A arg1, B arg2) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 2, 0, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -411,24 +329,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg2 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg1) -> function(arg1, arg2) }
-     */
-    static <A, B, R> PooledFunction<A, R> obtainFunction(
-            BiFunction<? super A, ? super B, ? extends R> function,
-            ArgumentPlaceholder<A> arg1, B arg2) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -465,24 +365,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg2) -> function(arg1, arg2) }
-     */
-    static <A, B, R> PooledFunction<B, R> obtainFunction(
-            BiFunction<? super A, ? super B, ? extends R> function,
-            A arg1, ArgumentPlaceholder<B> arg2) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -536,25 +418,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3) }
-     */
-    static <A, B, C, R> PooledSupplier<R> obtainSupplier(
-            TriFunction<? super A, ? super B, ? super C, ? extends R> function,
-            A arg1, B arg2, C arg3) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 3, 0, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -574,25 +437,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg1) -> function(arg1, arg2, arg3) }
-     */
-    static <A, B, C, R> PooledFunction<A, R> obtainFunction(
-            TriFunction<? super A, ? super B, ? super C, ? extends R> function,
-            ArgumentPlaceholder<A> arg1, B arg2, C arg3) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -612,25 +456,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg3 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg2) -> function(arg1, arg2, arg3) }
-     */
-    static <A, B, C, R> PooledFunction<B, R> obtainFunction(
-            TriFunction<? super A, ? super B, ? super C, ? extends R> function,
-            A arg1, ArgumentPlaceholder<B> arg2, C arg3) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -650,25 +475,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg3) -> function(arg1, arg2, arg3) }
-     */
-    static <A, B, C, R> PooledFunction<C, R> obtainFunction(
-            TriFunction<? super A, ? super B, ? super C, ? extends R> function,
-            A arg1, B arg2, ArgumentPlaceholder<C> arg3) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -724,26 +530,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4) }
-     */
-    static <A, B, C, D, R> PooledSupplier<R> obtainSupplier(
-            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 4, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -764,26 +550,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg1) -> function(arg1, arg2, arg3, arg4) }
-     */
-    static <A, B, C, D, R> PooledFunction<A, R> obtainFunction(
-            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
-            ArgumentPlaceholder<A> arg1, B arg2, C arg3, D arg4) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -804,26 +570,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg2) -> function(arg1, arg2, arg3, arg4) }
-     */
-    static <A, B, C, D, R> PooledFunction<B, R> obtainFunction(
-            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
-            A arg1, ArgumentPlaceholder<B> arg2, C arg3, D arg4) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -844,26 +590,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
-     * @param arg4 parameter supplied to {@code function} on call
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg3) -> function(arg1, arg2, arg3, arg4) }
-     */
-    static <A, B, C, D, R> PooledFunction<C, R> obtainFunction(
-            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
-            A arg1, B arg2, ArgumentPlaceholder<C> arg3, D arg4) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * {@link PooledConsumer} factory
      *
      * @param function non-capturing lambda(typically an unbounded method reference)
@@ -884,26 +610,6 @@
     }
 
     /**
-     * {@link PooledFunction} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 placeholder for a missing argument. Use {@link #__} to get one
-     * @return a {@link PooledFunction}, equivalent to lambda:
-     *         {@code (arg4) -> function(arg1, arg2, arg3, arg4) }
-     */
-    static <A, B, C, D, R> PooledFunction<D, R> obtainFunction(
-            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
-            A arg1, B arg2, C arg3, ArgumentPlaceholder<D> arg4) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -961,27 +667,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5) }
-     */
-    static <A, B, C, D, E, R> PooledSupplier<R> obtainSupplier(
-            QuintFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? extends R>
-                    function, A arg1, B arg2, C arg3, D arg4, E arg5) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 5, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, null, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1042,28 +727,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6) }
-     */
-    static <A, B, C, D, E, F, R> PooledSupplier<R> obtainSupplier(
-            HexFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                    ? extends R> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 6, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1126,30 +789,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
-     */
-    static <A, B, C, D, E, F, G, R> PooledSupplier<R> obtainSupplier(
-            HeptFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                    ? super G, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 7, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1215,31 +854,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @param arg8 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) }
-     */
-    static <A, B, C, D, E, F, G, H, R> PooledSupplier<R> obtainSupplier(
-            OctFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                                ? super G, ? super H, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 8, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
-                null, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1308,32 +922,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @param arg8 parameter supplied to {@code function} on call
-     * @param arg9 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) }
-     */
-    static <A, B, C, D, E, F, G, H, I, R> PooledSupplier<R> obtainSupplier(
-            NonaFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                                ? super G, ? super H, ? super I, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 9, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
-                arg9, null, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1404,33 +992,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @param arg8 parameter supplied to {@code function} on call
-     * @param arg9 parameter supplied to {@code function} on call
-     * @param arg10 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) }
-     */
-    static <A, B, C, D, E, F, G, H, I, J, R> PooledSupplier<R> obtainSupplier(
-            DecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                                ? super G, ? super H, ? super I, ? super J, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 10, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
-                arg9, arg10, null, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1504,36 +1065,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @param arg8 parameter supplied to {@code function} on call
-     * @param arg9 parameter supplied to {@code function} on call
-     * @param arg10 parameter supplied to {@code function} on call
-     * @param arg11 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
-     *         arg11) }
-     */
-    static <A, B, C, D, E, F, G, H, I, J, K, R> PooledSupplier<R> obtainSupplier(
-            UndecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                    ? super G, ? super H, ? super I, ? super J, ? super K, ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
-            K arg11) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
-                arg9, arg10, arg11, null);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
@@ -1611,38 +1142,6 @@
     }
 
     /**
-     * {@link PooledSupplier} factory
-     *
-     * @param function non-capturing lambda(typically an unbounded method reference)
-     *                 to be invoked on call
-     * @param arg1 parameter supplied to {@code function} on call
-     * @param arg2 parameter supplied to {@code function} on call
-     * @param arg3 parameter supplied to {@code function} on call
-     * @param arg4 parameter supplied to {@code function} on call
-     * @param arg5 parameter supplied to {@code function} on call
-     * @param arg6 parameter supplied to {@code function} on call
-     * @param arg7 parameter supplied to {@code function} on call
-     * @param arg8 parameter supplied to {@code function} on call
-     * @param arg9 parameter supplied to {@code function} on call
-     * @param arg10 parameter supplied to {@code function} on call
-     * @param arg11 parameter supplied to {@code function} on call
-     * @param arg12 parameter supplied to {@code function} on call
-     * @return a {@link PooledSupplier}, equivalent to lambda:
-     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
-     *         arg11) }
-     */
-    static <A, B, C, D, E, F, G, H, I, J, K, L, R> PooledSupplier<R> obtainSupplier(
-            DodecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
-                                ? super G, ? super H, ? super I, ? super J, ? super K, ? extends L,
-                                ? extends R> function,
-            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
-            K arg11, L arg12) {
-        return acquire(PooledLambdaImpl.sPool,
-                function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
-                arg9, arg10, arg11, arg12);
-    }
-
-    /**
      * Factory of {@link Message}s that contain an
      * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
      * {@link Message#getCallback internal callback}.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9471fae..f4c3928 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -48,7 +48,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in @nullable String imiId,
-            boolean allowsImplicitlySelectedSubtypes, int userId);
+            boolean allowsImplicitlyEnabledSubtypes, int userId);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
@@ -84,6 +84,7 @@
     void showInputMethodPickerFromSystem(in IInputMethodClient client,
             int auxiliarySubtypeMode, int displayId);
 
+    @EnforcePermission("TEST_INPUT_METHOD")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.TEST_INPUT_METHOD)")
     boolean isInputMethodPickerShownForTest();
@@ -97,6 +98,11 @@
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
             int userId);
 
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+    void setExplicitlyEnabledInputMethodSubtypes(String imeId, in int[] subtypeHashCodes,
+            int userId);
+
     // This is kept due to @UnsupportedAppUsage.
     // TODO(Bug 113914148): Consider removing this.
     int getInputMethodWindowVisibleHeight(in IInputMethodClient client);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index eb8e4fc..953b36b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -26,6 +26,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.PropertyInvalidatedCache;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.PasswordMetrics;
@@ -1777,4 +1778,16 @@
             re.rethrowFromSystemServer();
         }
     }
+
+    public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+        getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+    }
+
+    public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
+        getLockSettingsInternal().createNewUser(userId, userSerialNumber);
+    }
+
+    public void removeUser(@UserIdInt int userId) {
+        getLockSettingsInternal().removeUser(userId);
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 0a2c18f8..5b08bb1 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.admin.PasswordMetrics;
 
 import java.lang.annotation.Retention;
@@ -53,6 +54,37 @@
     // TODO(b/183140900) split store escrow key errors into detailed ones.
 
     /**
+     * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
+     * doesn't have an LSKF.
+     * <p>
+     * This doesn't throw an exception on failure; whether the storage has been unlocked can be
+     * determined by {@link StorageManager#isUserKeyUnlocked()}.
+     *
+     * @param userId the ID of the user whose storage to unlock
+     */
+    public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
+
+    /**
+     * Creates the locksettings state for a new user.
+     * <p>
+     * This includes creating a synthetic password and protecting it with an empty LSKF.
+     *
+     * @param userId the ID of the new user
+     * @param userSerialNumber the serial number of the new user
+     */
+    public abstract void createNewUser(@UserIdInt int userId, int userSerialNumber);
+
+    /**
+     * Removes the locksettings state for the given user.
+     * <p>
+     * This includes removing the user's synthetic password and any protectors that are protecting
+     * it.
+     *
+     * @param userId the ID of the user being removed
+     */
+    public abstract void removeUser(@UserIdInt int userId);
+
+    /**
      * Create an escrow token for the current user, which can later be used to unlock FBE
      * or change user password.
      *
diff --git a/core/jni/android_media_AudioProductStrategies.cpp b/core/jni/android_media_AudioProductStrategies.cpp
index 34be2a5..4b563d7 100644
--- a/core/jni/android_media_AudioProductStrategies.cpp
+++ b/core/jni/android_media_AudioProductStrategies.cpp
@@ -86,8 +86,8 @@
 
     // Audio Attributes Group array
     int attrGroupIndex = 0;
-    std::map<int /**attributesGroupIndex*/, std::vector<AudioAttributes> > groups;
-    for (const auto &attr : strategy.getAudioAttributes()) {
+    std::map<int /**attributesGroupIndex*/, std::vector<VolumeGroupAttributes> > groups;
+    for (const auto &attr : strategy.getVolumeGroupAttributes()) {
         int groupId = attr.getGroupId();
         int streamType = attr.getStreamType();
         const auto &iter = std::find_if(begin(groups), end(groups),
@@ -108,17 +108,17 @@
     jAudioAttributesGroups = env->NewObjectArray(numAttributesGroups, gAudioAttributesGroupClass, NULL);
 
     for (const auto &iter : groups) {
-        std::vector<AudioAttributes> audioAttributesGroups = iter.second;
-        jint numAttributes = audioAttributesGroups.size();
-        jint jGroupId = audioAttributesGroups.front().getGroupId();
-        jint jLegacyStreamType = audioAttributesGroups.front().getStreamType();
+        std::vector<VolumeGroupAttributes> volumeGroupAttributes = iter.second;
+        jint numAttributes = volumeGroupAttributes.size();
+        jint jGroupId = volumeGroupAttributes.front().getGroupId();
+        jint jLegacyStreamType = volumeGroupAttributes.front().getStreamType();
 
         jStatus = JNIAudioAttributeHelper::getJavaArray(env, &jAudioAttributes, numAttributes);
         if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
             goto exit;
         }
         for (size_t j = 0; j < static_cast<size_t>(numAttributes); j++) {
-            auto attributes = audioAttributesGroups[j].getAttributes();
+            auto attributes = volumeGroupAttributes[j].getAttributes();
 
             jStatus = JNIAudioAttributeHelper::nativeToJava(env, &jAudioAttribute, attributes);
             if (jStatus != AUDIO_JAVA_SUCCESS) {
diff --git a/core/jni/android_media_AudioVolumeGroups.cpp b/core/jni/android_media_AudioVolumeGroups.cpp
index 7098451..1252e89 100644
--- a/core/jni/android_media_AudioVolumeGroups.cpp
+++ b/core/jni/android_media_AudioVolumeGroups.cpp
@@ -94,6 +94,11 @@
     for (size_t j = 0; j < static_cast<size_t>(numAttributes); j++) {
         auto attributes = group.getAudioAttributes()[j];
 
+        // Native & Java audio attributes default initializers are not aligned for the source.
+        // Given the volume group class concerns only playback, this field must be equal to the
+        // default java initializer.
+        attributes.source = AUDIO_SOURCE_INVALID;
+
         jStatus = JNIAudioAttributeHelper::nativeToJava(env, &jAudioAttribute, attributes);
         if (jStatus != AUDIO_JAVA_SUCCESS) {
             goto exit;
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index f7a98d1..30d9ea1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -51,6 +51,21 @@
 
     virtual int handleEvent(int fd, int events, void* data);
 
+    /**
+     * A simple proxy that holds a weak reference to a looper callback.
+     */
+    class WeakLooperCallback : public LooperCallback {
+    protected:
+        virtual ~WeakLooperCallback();
+
+    public:
+        WeakLooperCallback(const wp<LooperCallback>& callback);
+        virtual int handleEvent(int fd, int events, void* data);
+
+    private:
+        wp<LooperCallback> mCallback;
+    };
+
 private:
     JNIEnv* mPollEnv;
     jobject mPollObj;
@@ -131,7 +146,8 @@
         if (events & CALLBACK_EVENT_OUTPUT) {
             looperEvents |= Looper::EVENT_OUTPUT;
         }
-        mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
+        mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents,
+                sp<WeakLooperCallback>::make(this),
                 reinterpret_cast<void*>(events));
     } else {
         mLooper->removeFd(fd);
@@ -162,6 +178,24 @@
 }
 
 
+// --- NativeMessageQueue::WeakLooperCallback ---
+
+NativeMessageQueue::WeakLooperCallback::WeakLooperCallback(const wp<LooperCallback>& callback) :
+        mCallback(callback) {
+}
+
+NativeMessageQueue::WeakLooperCallback::~WeakLooperCallback() {
+}
+
+int NativeMessageQueue::WeakLooperCallback::handleEvent(int fd, int events, void* data) {
+    sp<LooperCallback> callback = mCallback.promote();
+    if (callback != nullptr) {
+        return callback->handleEvent(fd, events, data);
+    }
+    return 0;
+}
+
+
 // ----------------------------------------------------------------------------
 
 sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index 1c61c7b..ffacd9c 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -22,6 +22,8 @@
 
 #include <array>
 
+static constexpr const char* kNullReplacement = "(null)";
+
 namespace android {
 
 inline static void sanitizeString(char* str) {
@@ -36,6 +38,11 @@
 
 template<typename F>
 inline static void withString(JNIEnv* env, jstring jstr, F callback) {
+    if (CC_UNLIKELY(jstr == nullptr)) {
+        callback(kNullReplacement);
+        return;
+    }
+
     // We need to handle the worst case of 1 character -> 4 bytes
     // So make a buffer of size 4097 and let it hold a string with a maximum length
     // of 1024. The extra last byte for the null terminator.
@@ -52,14 +59,14 @@
 
 static void android_os_Trace_nativeTraceCounter(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jlong value) {
-    withString(env, nameStr, [tag, value](char* str) {
+    withString(env, nameStr, [tag, value](const char* str) {
         atrace_int64(tag, str, value);
     });
 }
 
 static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass,
         jlong tag, jstring nameStr) {
-    withString(env, nameStr, [tag](char* str) {
+    withString(env, nameStr, [tag](const char* str) {
         atrace_begin(tag, str);
     });
 }
@@ -70,22 +77,22 @@
 
 static void android_os_Trace_nativeAsyncTraceBegin(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jint cookie) {
-    withString(env, nameStr, [tag, cookie](char* str) {
+    withString(env, nameStr, [tag, cookie](const char* str) {
         atrace_async_begin(tag, str, cookie);
     });
 }
 
 static void android_os_Trace_nativeAsyncTraceEnd(JNIEnv* env, jclass,
         jlong tag, jstring nameStr, jint cookie) {
-    withString(env, nameStr, [tag, cookie](char* str) {
+    withString(env, nameStr, [tag, cookie](const char* str) {
         atrace_async_end(tag, str, cookie);
     });
 }
 
 static void android_os_Trace_nativeAsyncTraceForTrackBegin(JNIEnv* env, jclass,
         jlong tag, jstring trackStr, jstring nameStr, jint cookie) {
-    withString(env, trackStr, [env, tag, nameStr, cookie](char* track) {
-        withString(env, nameStr, [tag, track, cookie](char* name) {
+    withString(env, trackStr, [env, tag, nameStr, cookie](const char* track) {
+        withString(env, nameStr, [tag, track, cookie](const char* name) {
             atrace_async_for_track_begin(tag, track, name, cookie);
         });
     });
@@ -93,7 +100,7 @@
 
 static void android_os_Trace_nativeAsyncTraceForTrackEnd(JNIEnv* env, jclass,
         jlong tag, jstring trackStr, jint cookie) {
-    withString(env, trackStr, [tag, cookie](char* track) {
+    withString(env, trackStr, [tag, cookie](const char* track) {
         atrace_async_for_track_end(tag, track, cookie);
     });
 }
@@ -108,15 +115,15 @@
 
 static void android_os_Trace_nativeInstant(JNIEnv* env, jclass,
         jlong tag, jstring nameStr) {
-    withString(env, nameStr, [tag](char* str) {
+    withString(env, nameStr, [tag](const char* str) {
         atrace_instant(tag, str);
     });
 }
 
 static void android_os_Trace_nativeInstantForTrack(JNIEnv* env, jclass,
         jlong tag, jstring trackStr, jstring nameStr) {
-    withString(env, trackStr, [env, tag, nameStr](char* track) {
-        withString(env, nameStr, [tag, track](char* name) {
+    withString(env, trackStr, [env, tag, nameStr](const char* track) {
+        withString(env, nameStr, [tag, track](const char* name) {
             atrace_instant_for_track(tag, track, name);
         });
     });
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 8c23b21..b60ec9f 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -39,6 +39,7 @@
 #include "androidfw/AssetManager2.h"
 #include "androidfw/AttributeResolution.h"
 #include "androidfw/MutexGuard.h"
+#include <androidfw/ResourceTimer.h>
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/ResourceUtils.h"
 
@@ -630,6 +631,7 @@
                                    jshort density, jobject typed_value,
                                    jboolean resolve_references) {
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(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()) {
@@ -1232,6 +1234,7 @@
   }
 
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  ResourceTimer _timer(ResourceTimer::Counter::RetrieveAttributes);
   ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
   auto result =
           RetrieveAttributes(assetmanager.get(), xml_parser, reinterpret_cast<uint32_t*>(attrs),
@@ -1293,6 +1296,10 @@
   } else {
     CHECK(style_count == 0) << "style_ids is null while style_count is non-zero";
   }
+  auto style_id_args_copy = std::vector<uint32_t>{style_id_args, style_id_args + style_count};
+  if (style_ids != nullptr) {
+      env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT);
+  }
 
   jboolean* force_args = nullptr;
   if (force != nullptr) {
@@ -1305,15 +1312,14 @@
   } else {
     CHECK(style_count == 0) << "force is null while style_count is non-zero";
   }
-
-  auto theme = reinterpret_cast<Theme*>(theme_ptr);
-  theme->Rebase(&(*assetmanager), style_id_args, force_args, static_cast<size_t>(style_count));
-  if (style_ids != nullptr) {
-    env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT);
-  }
+  auto force_args_copy = std::vector<jboolean>{force_args, force_args + style_count};
   if (force != nullptr) {
     env->ReleasePrimitiveArrayCritical(force, force_args, JNI_ABORT);
   }
+
+  auto theme = reinterpret_cast<Theme*>(theme_ptr);
+  theme->Rebase(&(*assetmanager), style_id_args_copy.data(), force_args_copy.data(),
+                static_cast<size_t>(style_count));
 }
 
 static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_asset_manager_ptr,
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index b9d5ee4..9501c8d 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1252,6 +1252,20 @@
     return fd;
 }
 
+void android_os_Process_freezeCgroupUID(JNIEnv* env, jobject clazz, jint uid, jboolean freeze) {
+    bool success = true;
+
+    if (freeze) {
+        success = SetUserProfiles(uid, {"Frozen"});
+    } else {
+        success = SetUserProfiles(uid, {"Unfrozen"});
+    }
+
+    if (!success) {
+        jniThrowRuntimeException(env, "Could not apply user profile");
+    }
+}
+
 static const JNINativeMethod methods[] = {
         {"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
         {"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
@@ -1293,6 +1307,7 @@
         {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
         {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
         {"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
+        {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
 };
 
 int register_android_os_Process(JNIEnv* env)
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 8913300..5a444bb 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -28,6 +28,9 @@
 #include <stdio.h>
 
 namespace android {
+constexpr int kNullDocument = UNEXPECTED_NULL;
+// The reason not to ResXMLParser::BAD_DOCUMENT which is -1 is that other places use the same value.
+constexpr int kBadDocument = BAD_VALUE;
 
 // ----------------------------------------------------------------------------
 
@@ -92,9 +95,7 @@
     return reinterpret_cast<jlong>(st);
 }
 
-static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz,
-                                             jlong token)
-{
+static jint android_content_XmlBlock_nativeNext(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
         return ResXMLParser::END_DOCUMENT;
@@ -121,14 +122,10 @@
     } while (true);
 
 bad:
-    jniThrowException(env, "org/xmlpull/v1/XmlPullParserException",
-            "Corrupt XML binary file");
-    return ResXMLParser::BAD_DOCUMENT;
+    return kBadDocument;
 }
 
-static jint android_content_XmlBlock_nativeGetNamespace(JNIEnv* env, jobject clazz,
-                                                   jlong token)
-{
+static jint android_content_XmlBlock_nativeGetNamespace(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
         return -1;
@@ -137,9 +134,7 @@
     return static_cast<jint>(st->getElementNamespaceID());
 }
 
-static jint android_content_XmlBlock_nativeGetName(JNIEnv* env, jobject clazz,
-                                                jlong token)
-{
+static jint android_content_XmlBlock_nativeGetName(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
         return -1;
@@ -148,9 +143,7 @@
     return static_cast<jint>(st->getElementNameID());
 }
 
-static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz,
-                                                jlong token)
-{
+static jint android_content_XmlBlock_nativeGetText(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
         return -1;
@@ -159,97 +152,80 @@
     return static_cast<jint>(st->getTextID());
 }
 
-static jint android_content_XmlBlock_nativeGetLineNumber(JNIEnv* env, jobject clazz,
-                                                         jlong token)
-{
+static jint android_content_XmlBlock_nativeGetLineNumber(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getLineNumber());
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeCount(JNIEnv* env, jobject clazz,
-                                                          jlong token)
-{
+static jint android_content_XmlBlock_nativeGetAttributeCount(
+        CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeCount());
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeNamespace(JNIEnv* env, jobject clazz,
-                                                                 jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeNamespace(
+        CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeNamespaceID(idx));
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeName(JNIEnv* env, jobject clazz,
-                                                         jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeName(CRITICAL_JNI_PARAMS_COMMA jlong token,
+                                                            jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeNameID(idx));
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeResource(JNIEnv* env, jobject clazz,
-                                                             jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeResource(
+        CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeNameResID(idx));
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeDataType(JNIEnv* env, jobject clazz,
-                                                                jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeDataType(
+        CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeDataType(idx));
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeData(JNIEnv* env, jobject clazz,
-                                                            jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeData(CRITICAL_JNI_PARAMS_COMMA jlong token,
+                                                            jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeData(idx));
 }
 
-static jint android_content_XmlBlock_nativeGetAttributeStringValue(JNIEnv* env, jobject clazz,
-                                                                   jlong token, jint idx)
-{
+static jint android_content_XmlBlock_nativeGetAttributeStringValue(
+        CRITICAL_JNI_PARAMS_COMMA jlong token, jint idx) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     return static_cast<jint>(st->getAttributeValueStringID(idx));
@@ -286,39 +262,32 @@
     return idx;
 }
 
-static jint android_content_XmlBlock_nativeGetIdAttribute(JNIEnv* env, jobject clazz,
-                                                          jlong token)
-{
+static jint android_content_XmlBlock_nativeGetIdAttribute(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     ssize_t idx = st->indexOfID();
     return idx >= 0 ? static_cast<jint>(st->getAttributeValueStringID(idx)) : -1;
 }
 
-static jint android_content_XmlBlock_nativeGetClassAttribute(JNIEnv* env, jobject clazz,
-                                                             jlong token)
-{
+static jint android_content_XmlBlock_nativeGetClassAttribute(
+        CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     ssize_t idx = st->indexOfClass();
     return idx >= 0 ? static_cast<jint>(st->getAttributeValueStringID(idx)) : -1;
 }
 
-static jint android_content_XmlBlock_nativeGetStyleAttribute(JNIEnv* env, jobject clazz,
-                                                             jlong token)
-{
+static jint android_content_XmlBlock_nativeGetStyleAttribute(
+        CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
+        return kNullDocument;
     }
 
     ssize_t idx = st->indexOfStyle();
@@ -336,9 +305,7 @@
         ? value.data : 0;
 }
 
-static jint android_content_XmlBlock_nativeGetSourceResId(JNIEnv* env, jobject clazz,
-                                                          jlong token)
-{
+static jint android_content_XmlBlock_nativeGetSourceResId(CRITICAL_JNI_PARAMS_COMMA jlong token) {
     ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
     if (st == NULL) {
         return 0;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4ad995a..b11f22a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1528,27 +1528,6 @@
     transaction->reparent(ctrl, newParent);
 }
 
-static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
-                                   jintArray jHdrTypes) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
-    if (token == nullptr || jHdrTypes == nullptr) return;
-
-    int* hdrTypes = env->GetIntArrayElements(jHdrTypes, 0);
-    int numHdrTypes = env->GetArrayLength(jHdrTypes);
-
-    std::vector<ui::Hdr> hdrTypesVector;
-    for (int i = 0; i < numHdrTypes; i++) {
-        hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
-    }
-    env->ReleaseIntArrayElements(jHdrTypes, hdrTypes, 0);
-
-    status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
-    if (error != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/SecurityException",
-                             "ACCESS_SURFACE_FLINGER is missing");
-    }
-}
-
 static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) {
     bool isBootDisplayModeSupported = false;
     SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported);
@@ -2056,8 +2035,6 @@
             (void*)nativeSetGameContentType },
     {"nativeGetCompositionDataspaces", "()[I",
             (void*)nativeGetCompositionDataspaces},
-    {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
-                (void*)nativeOverrideHdrTypes },
     {"nativeClearContentFrameStats", "(J)Z",
             (void*)nativeClearContentFrameStats },
     {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
diff --git a/core/jni/com_android_internal_content_om_OverlayConfig.cpp b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
index b37269c..52a933a 100644
--- a/core/jni/com_android_internal_content_om_OverlayConfig.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
@@ -66,23 +66,23 @@
     argv.emplace_back("--ignore-overlayable");
   }
 
-  const auto result = ExecuteBinary(argv);
+  auto result = ExecuteBinary(argv);
   if (!result) {
       LOG(ERROR) << "failed to execute idmap2";
       return nullptr;
   }
 
-  if (result->status != 0) {
-      LOG(ERROR) << "idmap2: " << result->stderr_str;
+  if (result.status != 0) {
+      LOG(ERROR) << "idmap2: " << result.stderr_str;
       return nullptr;
   }
 
   // Return the paths of the idmaps created or updated during the idmap invocation.
   std::vector<std::string> idmap_paths;
-  std::istringstream input(result->stdout_str);
+  std::istringstream input(std::move(result.stdout_str));
   std::string path;
   while (std::getline(input, path)) {
-    idmap_paths.push_back(path);
+      idmap_paths.push_back(std::move(path));
   }
 
   jobjectArray array = env->NewObjectArray(idmap_paths.size(), g_stringClass, nullptr);
@@ -90,11 +90,11 @@
     return nullptr;
   }
   for (size_t i = 0; i < idmap_paths.size(); i++) {
-    const std::string path = idmap_paths[i];
-    jstring java_string = env->NewStringUTF(path.c_str());
-    if (env->ExceptionCheck()) {
-      return nullptr;
-    }
+      const std::string& path = idmap_paths[i];
+      jstring java_string = env->NewStringUTF(path.c_str());
+      if (env->ExceptionCheck()) {
+          return nullptr;
+      }
     env->SetObjectArrayElement(array, i, java_string);
     env->DeleteLocalRef(java_string);
   }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 4bbfee2..59e01bf 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -61,7 +61,6 @@
 import "frameworks/base/core/proto/android/privacy.proto";
 import "frameworks/base/core/proto/android/section.proto";
 import "frameworks/base/proto/src/ipconnectivity.proto";
-import "packages/modules/Permission/service/proto/role_service.proto";
 
 package android.os;
 
@@ -358,10 +357,7 @@
         (section).userdebug_and_eng_only = true
     ];
 
-    optional com.android.role.RoleServiceDumpProto role = 3024 [
-        (section).type = SECTION_DUMPSYS,
-        (section).args = "role --proto"
-    ];
+    reserved 3024;
 
     optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [
         (section).type = SECTION_DUMPSYS,
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 93ce783..7e17840 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -130,6 +130,10 @@
 
         // Allow overlay to add resource
         "--auto-add-overlay",
+
+        // Framework resources benefit tremendously from enabling sparse encoding, saving tens
+        // of MBs in size and RAM use.
+        "--enable-sparse-encoding",
     ],
 
     resource_zips: [
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4d41c30..f0b1b2a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -818,6 +818,8 @@
     <!-- Added in U -->
     <protected-broadcast android:name="android.intent.action.PROFILE_ADDED" />
     <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" />
+    <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
+    <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -3049,6 +3051,10 @@
     <permission android:name="android.permission.QUERY_ADMIN_POLICY"
                 android:protectionLevel="signature|role" />
 
+    <!-- @SystemApi @hide Allows an application to exempt apps from platform restrictions.-->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
+                android:protectionLevel="signature|role" />
+
     <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
     <permission android:name="android.permission.PROVISION_DEMO_DEVICE"
                 android:protectionLevel="signature|setup" />
@@ -3509,7 +3515,7 @@
      application-specific locale configs.
      <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|installer" />
 
     <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
     <p>Not for use by third-party applications. -->
@@ -6810,8 +6816,9 @@
                   android:exported="false">
         </activity>
 
-        <activity android:name="com.android.server.logcat.LogAccessDialogActivity"
+        <activity android:name="com.android.internal.app.LogAccessDialogActivity"
                   android:theme="@style/Theme.Translucent.NoTitleBar"
+                  android:process=":ui"
                   android:excludeFromRecents="true"
                   android:exported="false">
         </activity>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 0b9386c3..21643c9 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gee <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang tot alle toestelloglêers?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gee eenmalige toegang"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Moenie toelaat nie"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nAs jy nie vir hierdie program toegang tot alle toestelloglêers gee nie, het die program steeds toegang tot eie loglêers. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nAs jy nie vir hierdie program toegang tot alle toestelloglêers gee nie, het die program steeds toegang tot eie loglêers. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting, en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nHierdie program het steeds toegang tot eie loglêers as jy nie vir hierdie program toegang tot alle toestelloglêers gee nie. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel.\n\nKom meer te wete by g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Moenie weer wys nie"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil <xliff:g id="APP_2">%2$s</xliff:g>-skyfies wys"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Wysig"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 1e8d681..af1cdad 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ይፈቀድለት?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"የአንድ ጊዜ መዳረሻን ፍቀድ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"አትፍቀድ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸውን መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የእርስዎ መሣሪያ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸውን መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የእርስዎ መሣሪያ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ዳግም አታሳይ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን ማሳየት ይፈልጋል"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"አርትዕ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1529b0d..81b73ca 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2054,7 +2054,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"هل تريد السماح لتطبيق <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> بالوصول إلى جميع سجلّات الجهاز؟"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"السماح بالوصول إلى السجلّ لمرة واحدة"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"عدم السماح"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"عدم الإظهار مرة أخرى"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"يريد تطبيق <xliff:g id="APP_0">%1$s</xliff:g> عرض شرائح تطبيق <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"تعديل"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index a854ea6..192d07a3 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ক আটাইবোৰ ডিভাইচৰ লগ এক্সেছ কৰাৰ অনুমতি প্ৰদান কৰিবনে?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"কেৱল এবাৰ এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি নিদিব"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"পুনৰ নেদেখুৱাব"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>এ <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাব খুজিছে"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"সম্পাদনা কৰক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 1449500..29124f2 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tətbiqinin bütün cihaz qeydlərinə girişinə icazə verilsin?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Birdəfəlik girişə icazə verin"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İcazə verməyin"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər.\n\nƏtraflı məlumat: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daha göstərməyin"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> tətbiqindən bölmələr göstərmək istəyir"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redaktə edin"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index bcaea5d..6b5d8d2 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite da dozvolite aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim evidencijama uređaja?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dozvoli"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Izmeni"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 48dd891..8a14637 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -190,7 +190,7 @@
     <string name="network_logging_notification_text" msgid="1327373071132562512">"Ваша арганізацыя кіруе гэтай прыладай і можа сачыць за сеткавым трафікам. Дакраніцеся для атрымання дадатковай інфармацыі."</string>
     <string name="location_changed_notification_title" msgid="3620158742816699316">"Праграмы могуць атрымліваць даныя пра ваша месцазнаходжанне"</string>
     <string name="location_changed_notification_text" msgid="7158423339982706912">"Каб даведацца больш, звярніцеся да ІТ-адміністратара"</string>
-    <string name="geofencing_service" msgid="3826902410740315456">"Служба вызначэння геаперыметра"</string>
+    <string name="geofencing_service" msgid="3826902410740315456">"Сэрвіс геазаніравання"</string>
     <string name="country_detector" msgid="7023275114706088854">"Дэтэктар краіны"</string>
     <string name="location_service" msgid="2439187616018455546">"Служба геалакацыі"</string>
     <string name="gnss_service" msgid="8907781262179951385">"Служба GNSS"</string>
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Дазволіць праграме \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" мець доступ да ўсіх журналаў прылады?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дазволіць аднаразовы доступ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дазваляць"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больш не паказваць"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Праграма <xliff:g id="APP_0">%1$s</xliff:g> запытвае дазвол на паказ зрэзаў праграмы <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Рэдагаваць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9304969..9018324 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се разреши ли на <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> достъп до всички регистрационни файлове за устройството?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешаване на еднократен достъп"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Забраняване"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството пак може да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството пак може да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Да не се показва пак"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> иска да показва части от <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редактиране"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 09033e9..70b64fd 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিতে চান?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"এককালীন অ্যাক্সেসের অনুমতি দিন"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি দেবেন না"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই সব ডিভাইসের লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারকও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই সব ডিভাইসের লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারকও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ, সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারক এখনও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।\n\ng.co/android/devicelogs লিঙ্ক থেকে আরও জানুন।"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"আর দেখতে চাই না"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটি <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখাতে চায়"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"এডিট করুন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index d741095..89b6b70 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Dozvoliti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dozvoliti"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i isprave probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, zato pristup svim zapisnicima uređaja dozvolite samo aplikacijama kojima vjerujete. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje biti u stanju pristupiti nekim zapisnicima ili podacima na uređaju."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i isprave probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, zato pristup svim zapisnicima uređaja dozvolite samo aplikacijama kojima vjerujete. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje biti u stanju pristupiti nekim zapisnicima ili podacima na uređaju."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i riješe probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke. Zbog toga pristup svim zapisnicima uređaja dozvolite samo aplikacijama koje smatrate pouzdanima. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje moći pristupiti nekim zapisnicima ili podacima na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a334461..934ae21 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1857,8 +1857,8 @@
     <string name="confirm_battery_saver" msgid="5247976246208245754">"D\'acord"</string>
     <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string>
     <string name="battery_saver_description" msgid="8518809702138617167">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string>
-    <string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Economitzador de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string>
-    <string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Economitzador de dades?"</string>
+    <string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Estalvi de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string>
+    <string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Estalvi de dades?"</string>
     <string name="data_saver_enable_button" msgid="4399405762586419726">"Activa"</string>
     <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Durant 1 minut (fins a les {formattedTime})}other{Durant # minuts (fins a les {formattedTime})}}"</string>
     <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Durant 1 min (fins a les {formattedTime})}other{Durant # min (fins a les {formattedTime})}}"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vols permetre que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> accedeixi a tots els registres del dispositiu?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permet l\'accés únic"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permetis"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No tornis a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vol mostrar porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edita"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 831ccab..cbff626 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Povolit aplikaci <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> přístup ke všem protokolům zařízení?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povolit jednorázový přístup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovolovat"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Příště nezobrazovat"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikace <xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Upravit"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index b35719d..3b21d5b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1949,9 +1949,9 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Android TV-indstillingerne er ikke tilgængelige"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Tabletindstillingerne er ikke tilgængelige"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Telefonindstillingerne er ikke tilgængelige"</string>
-    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Du har ikke adgang til denne app 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">"Du har ikke adgang til denne app 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">"Du har ikke adgang til denne app 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" 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_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>
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du give <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> adgang til alle enhedslogs?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillad engangsadgang"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillad ikke"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed.\n\nFå flere oplysninger på g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vis ikke igen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> anmoder om tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Rediger"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 2d151d9..16f312d 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> den Zugriff auf alle Geräteprotokolle erlauben?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Einmaligen Zugriff zulassen"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nicht zulassen"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen, daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen, daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nicht mehr anzeigen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> möchte Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzeigen"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Bearbeiten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index d3a9c17..095af40 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Να επιτρέπεται στο <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Να επιτρέπεται η πρόσβαση για μία φορά"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Να μην επιτραπεί"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή σε πληροφορίες στη συσκευή σας.\n\nΜάθετε περισσότερα στη διεύθυνση g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Να μην εμφανισ. ξανά"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Η εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> θέλει να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Επεξεργασία"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index fcc6cdd..5349d07 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index de1516c..8015681 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index ef91ac2..7e5ebe4 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 6d7f422..904f07f 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 4fee71d..b5051fd 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ to access all device logs?‎‏‎‎‏‎"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Allow one-time access‎‏‎‎‏‎"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎Don’t allow‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Learn more at g.co/android/devicelogs.‎‏‎‎‏‎"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎Don’t show again‎‏‎‎‏‎"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ wants to show ‎‏‎‎‏‏‎<xliff:g id="APP_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎ slices‎‏‎‎‏‎"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎Edit‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index efaaa97..9d78de3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Quieres permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso por única vez"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos ellos las apps que sean de tu confianza. \n\nSi no permites que esta app acceda a todos los registros del dispositivo, aún puede acceder a sus propios registros. Además, es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos ellos las apps que sean de tu confianza. \n\nSi no permites que esta app acceda a todos los registros del dispositivo, aún puede acceder a sus propios registros. Además, es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos los registros las apps que sean de tu confianza. \n\nTen en cuenta que la app puede acceder a sus propios registros incluso si no permites que acceda a todos los registros del dispositivo. También es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index e6c088f..f49de83 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -2051,7 +2051,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir el acceso una vez"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 7f761c4..70ad94e 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Kas anda rakendusele <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> juurdepääs kõigile seadmelogidele?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Luba ühekordne juurdepääs"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ära luba"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ära kuva uuesti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Rakendus <xliff:g id="APP_0">%1$s</xliff:g> soovib näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muuda"</string>
@@ -2289,7 +2291,7 @@
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Rakendus <xliff:g id="APP">%1$s</xliff:g> töötab taustal. Puudutage akukasutuse haldamiseks."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> võib aku tööiga mõjutada. Puudutage aktiivsete rakenduste ülevaatamiseks."</string>
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Vaadake aktiivseid rakendusi"</string>
-    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde"</string>
+    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tahvelarvuti kaamerale juurde"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Sellele ei pääse voogesituse ajal juurde. Proovige juurde pääseda oma telefonis."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Süsteemi vaikeseade"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index ee07ce1..8b447d1 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -51,7 +51,7 @@
     </plurals>
     <string name="imei" msgid="2157082351232630390">"IMEIa"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
-    <string name="ClipMmi" msgid="4110549342447630629">"Sarrerako deien identifikazio-zerbitzua"</string>
+    <string name="ClipMmi" msgid="4110549342447630629">"Deitzailearen identitatea (jasotako deiak)"</string>
     <string name="ClirMmi" msgid="6752346475055446417">"Ezkutatu irteerako deitzailearen identitatea"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"Konektatutako linearen IDa"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"Konektatutako linearen ID murriztapena"</string>
@@ -66,12 +66,12 @@
     <string name="RuacMmi" msgid="1876047385848991110">"Nahigabeko dei gogaikarriak ukatzea"</string>
     <string name="CndMmi" msgid="185136449405618437">"Deitzailearen zenbakia ematea"</string>
     <string name="DndMmi" msgid="8797375819689129800">"Ez molestatzeko modua"</string>
-    <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"Deien identifikazio-zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenekin"</string>
-    <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Deien identifikazio-zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenik gabe"</string>
-    <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Deien identifikazio-zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenekin"</string>
-    <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deien identifikazio-zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe"</string>
+    <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenekin"</string>
+    <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Deitzailearen identitatea adierazteko zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenik gabe."</string>
+    <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenekin."</string>
+    <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
-    <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu deien identifikazio-zerbitzuaren ezarpena aldatu."</string>
+    <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorreko datu-zerbitzurik"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ezin da egin larrialdi-deirik"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ez dago ahots-deien zerbitzurik"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gailuko erregistro guztiak atzitzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eman behin erabiltzeko baimena"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ez eman baimenik"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ez erakutsi berriro"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> aplikazioak <xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakutsi nahi ditu"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editatu"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 82c716f..9007d8e 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1491,8 +1491,8 @@
     <string name="vpn_title_long" msgid="6834144390504619998">"‏VPN را <xliff:g id="APP">%s</xliff:g> فعال کرده است"</string>
     <string name="vpn_text" msgid="2275388920267251078">"برای مدیریت شبکه ضربه بزنید."</string>
     <string name="vpn_text_long" msgid="278540576806169831">"به <xliff:g id="SESSION">%s</xliff:g> متصل شد. برای مدیریت شبکه ضربه بزنید."</string>
-    <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"‏در حال اتصال VPN همیشه فعال…"</string>
-    <string name="vpn_lockdown_connected" msgid="2853127976590658469">"‏VPN همیشه فعال متصل شد"</string>
+    <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"‏درحال اتصال به VPN همیشه روشن…"</string>
+    <string name="vpn_lockdown_connected" msgid="2853127976590658469">"‏VPN همیشه روشن متصل شد"</string>
     <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"‏از «VPN همیشه روشن» قطع شد"</string>
     <string name="vpn_lockdown_error" msgid="4453048646854247947">"‏به «VPN همیشه روشن» متصل نشد"</string>
     <string name="vpn_lockdown_config" msgid="8331697329868252169">"‏تغییر شبکه یا تنظیمات VPN"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"به <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> اجازه می‌دهید به همه گزارش‌های دستگاه دسترسی داشته باشد؟"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"مجاز کردن دسترسی یک‌باره"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازه ندادن"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوباره نشان داده نشود"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> می‌خواهد تکه‌های <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ویرایش"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5988783..5afb309 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Saako <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> pääsyn kaikkiin laitelokeihin?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Salli kertaluonteinen pääsy"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Älä salli"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Älä näytä uudelleen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> haluaa näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muokkaa"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 75ec39a..fc4bd0a 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à l\'ensemble des journaux de l\'appareil?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil.\n\nPour en savoir plus, consultez la page g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 427ae2d..a30530d 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2051,7 +2051,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à tous les journaux de l\'appareil ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre les problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre les problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index f99b755b..33c5cec 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -208,7 +208,7 @@
     <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Opcións da tableta"</string>
     <string name="power_dialog" product="tv" msgid="7792839006640933763">"Opcións de Android TV"</string>
     <string name="power_dialog" product="default" msgid="1107775420270203046">"Opcións do teléfono"</string>
-    <string name="silent_mode" msgid="8796112363642579333">"Modo de silencio"</string>
+    <string name="silent_mode" msgid="8796112363642579333">"Modo silencioso"</string>
     <string name="turn_on_radio" msgid="2961717788170634233">"Activar a conexión sen fíos"</string>
     <string name="turn_off_radio" msgid="7222573978109933360">"Desactivar a conexión sen fíos"</string>
     <string name="screen_lock" msgid="2072642720826409809">"Bloqueo de pantalla"</string>
@@ -252,7 +252,7 @@
     <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundos.}}"</string>
     <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Realizouse a captura de pantalla co informe de erros"</string>
     <string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Produciuse un erro ao realizar a captura de pantalla co informe de erros"</string>
-    <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo de silencio"</string>
+    <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo silencioso"</string>
     <string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"O son está desactivado"</string>
     <string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"O son está activado"</string>
     <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Modo avión"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Queres permitir que a aplicación <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos os rexistros do dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso unha soa vez"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non amosar outra vez"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quere mostrar fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 96b9e0f..c22f155 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી આપવી છે?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"એક-વખતના ઍક્સેસની મંજૂરી આપો"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"મંજૂરી આપશો નહીં"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે.\n\ng.co/android/devicelogs પર વધુ જાણો."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ફરીથી બતાવશો નહીં"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>એ <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવા માગે છે"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ફેરફાર કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 4e05cd4..c47322f 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"क्या <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> को डिवाइस लॉग का ऐक्सेस देना है?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक बार ऐक्सेस करने की अनुमति दें"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति न दें"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"डिवाइस लॉग में, आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें ठीक करने के लिए करते हैं.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिवाइस लॉग में, आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें ठीक करने के लिए करते हैं.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फिर से न दिखाएं"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाना चाहता है"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"बदलाव करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 0a557d6..f4bcfc5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite li dopustiti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Omogući jednokratni pristup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dopustiti"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> želi prikazivati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index f1388f6..b2dd4b1 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Engedélyezi a(z) <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> számára, hogy hozzáférjen az összes eszköznaplóhoz?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Egyszeri hozzáférés engedélyezése"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tiltás"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz.\n\nTovábbi információ: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne jelenjen meg újra"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"A(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazás részleteket szeretne megjeleníteni a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Szerkesztés"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 14fb55c..964abb1 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Հասանելի դարձնե՞լ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> հավելվածին սարքի բոլոր մատյանները"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Թույլատրել մեկանգամյա մուտքը"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Չթույլատրել"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Այլևս ցույց չտալ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> հավելվածն ուզում է ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Փոփոխել"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 58d91c8..aba836a 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Izinkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log perangkat?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Izinkan akses satu kali"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan izinkan"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tampilkan lagi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ingin menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 7466295..3440f18 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Veita <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aðgang að öllum annálum í tækinu?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leyfa aðgang í eitt skipti"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ekki leyfa"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ekki sýna aftur"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Breyta"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 3484165..d5766fc 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Consentire all\'app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> di accedere a tutti i log del dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Consenti accesso una tantum"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non consentire"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo.\n\nScopri di più all\'indirizzo g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non mostrare più"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"L\'app <xliff:g id="APP_0">%1$s</xliff:g> vuole mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifica"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0c62ea2..500a3a2 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1953,13 +1953,13 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"ההגדרות של הטלפון לא זמינות"</string>
     <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" product="default" msgid="6159168735030739398">"אי אפשר לגשת לאפליקציה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g> כרגע. במקום זאת, אפשר לנסות בטלפון."</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>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"‏אי אפשר לגשת להגדרה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g>. במקום זאת, יש לנסות במכשיר Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"אי אפשר לגשת להגדרה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g>. במקום זאת, יש לנסות בטאבלט."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"אי אפשר לגשת להגדרה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g>. במקום זאת, יש לנסות בטלפון."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"אי אפשר לגשת להגדרה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g>. במקום זאת, אפשר לנסות בטלפון."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"יש עדכון חדש?"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"יש לך הודעות חדשות"</string>
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"לתת לאפליקציה <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> הרשאת גישה לכל יומני המכשיר?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"הרשאת גישה חד-פעמית"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"אין אישור"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל יומני המכשיר רק לאפליקציות מהימנות. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל יומני המכשיר רק לאפליקציות מהימנות. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"אין להציג שוב"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> רוצה להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"עריכה"</string>
@@ -2293,7 +2295,7 @@
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"כדאי לבדוק את האפליקציות הפעילות"</string>
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"לא ניתן לגשת למצלמה של הטלפון מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"לא ניתן לגשת למצלמה של הטאבלט מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
-    <string name="vdm_secure_window" msgid="161700398158812314">"אי אפשר לגשת לתוכן המאובטח הזה בזמן סטרימינג. במקום זאת, יש לנסות בטלפון."</string>
+    <string name="vdm_secure_window" msgid="161700398158812314">"אי אפשר לגשת לתוכן המאובטח הזה בזמן סטרימינג. במקום זאת, אפשר לנסות בטלפון."</string>
     <string name="system_locale_title" msgid="711882686834677268">"ברירת המחדל של המערכת"</string>
     <string name="default_card_name" msgid="9198284935962911468">"כרטיס <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 94c6bb0..9ebe266 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> にすべてのデバイスログへのアクセスを許可しますか?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"1 回限りのアクセスを許可"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"許可しない"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合も、このアプリはアプリ独自のログにアクセスできます。また、デバイスのメーカーもデバイスの一部のログや情報にアクセスできる可能性があります。"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合も、このアプリはアプリ独自のログにアクセスできます。また、デバイスのメーカーもデバイスの一部のログや情報にアクセスできる可能性があります。"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合でも、このアプリはアプリ独自のログにアクセスできます。また、デバイスの製造メーカーもデバイスの一部のログや情報にアクセスできる可能性があります。\n\n詳しくは、g.co/android/devicelogs をご覧ください。"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"次回から表示しない"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」が「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示をリクエストしています"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編集"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 576c61e..61b6d2d 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"გსურთ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-ს მიანიჭოთ მოწყობილობის ყველა ჟურნალზე წვდომა?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ერთჯერადი წვდომის დაშვება"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"არ დაიშვას"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა თქვენს ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა თქვენს ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა საკუთარ ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა.\n\n შეიტყვეთ მეტი g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"აღარ გამოჩნდეს"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>-ს სურს, გაჩვენოთ <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"რედაქტირება"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 535996f..1bce8fe 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -72,7 +72,7 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string>
-    <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік деректер қызметі жоқ"</string>
+    <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік интернет қызметі жоқ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Жедел қызметке қоңырау шалу қолжетімді емес"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дауыстық қоңыраулар қызметі жоқ"</string>
     <string name="RestrictedOnAllVoiceTitle" msgid="3982069078579103087">"Дауыс қызметі немесе жедел қызметке қоңырау шалу мүмкіндігі жоқ"</string>
@@ -85,7 +85,7 @@
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"Дабылдар"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"Қоңырауды басқа нөмірге бағыттау"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Шұғыл кері қоңырау шалу режимі"</string>
-    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Мобильдік деректер күйі"</string>
+    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Мобильдік интернет күйі"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"SMS хабарлары"</string>
     <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Дауыстық пошта хабарлары"</string>
     <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi қоңыраулары"</string>
@@ -1306,7 +1306,7 @@
     <string name="network_switch_metered_detail" msgid="1358296010128405906">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string>
     <string name="network_switch_metered_toast" msgid="501662047275723743">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string>
   <string-array name="network_switch_type_name">
-    <item msgid="2255670471736226365">"мобильдік деректер"</item>
+    <item msgid="2255670471736226365">"мобильдік интернет"</item>
     <item msgid="5520925862115353992">"Wi-Fi"</item>
     <item msgid="1055487873974272842">"Bluetooth"</item>
     <item msgid="1616528372438698248">"Ethernet"</item>
@@ -1573,7 +1573,7 @@
     <string name="extract_edit_menu_button" msgid="63954536535863040">"Өзгерту"</string>
     <string name="data_usage_warning_title" msgid="9034893717078325845">"Дерек шығыны туралы ескерту"</string>
     <string name="data_usage_warning_body" msgid="1669325367188029454">"Деректің <xliff:g id="APP">%s</xliff:g> пайдаландыңыз"</string>
-    <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Мобильдік деректер шегіне жетті"</string>
+    <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Мобильдік интернет шегіне жетті"</string>
     <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Wi-Fi деректер шегіне жеттіңіз"</string>
     <string name="data_usage_limit_body" msgid="3567699582000085710">"Деректер жіберу қалған цикл үшін тоқтатылды"</string>
     <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Мобильдік дерек шегінен астыңыз"</string>
@@ -1581,7 +1581,7 @@
     <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Сіз <xliff:g id="SIZE">%s</xliff:g> шегінен асып кеттіңіз"</string>
     <string name="data_usage_restricted_title" msgid="126711424380051268">"Фондық деректер шектелген"</string>
     <string name="data_usage_restricted_body" msgid="5338694433686077733">"Шектеуді жою үшін түртіңіз."</string>
-    <string name="data_usage_rapid_title" msgid="2950192123248740375">"Мобильдік деректер көп жұмсалды"</string>
+    <string name="data_usage_rapid_title" msgid="2950192123248740375">"Мобильдік интернет көп жұмсалды"</string>
     <string name="data_usage_rapid_body" msgid="3886676853263693432">"Қолданбаларыңыз деректерді әдеттегіден көбірек пайдаланды"</string>
     <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"<xliff:g id="APP">%s</xliff:g> қолданбасы деректерді әдеттегіден көбірек пайдаланды"</string>
     <string name="ssl_certificate" msgid="5690020361307261997">"Қауіпсіздік сертификаты"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> қолданбасына барлық құрылғының журналын пайдалануға рұқсат берілсін бе?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бір реттік пайдалану рұқсатын беру"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Рұқсат бермеу"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Қайта көрсетілмесін"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасы <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсеткісі келеді"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Өзгерту"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 09c4478..a4913c3 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"អនុញ្ញាតឱ្យ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ឬ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"អនុញ្ញាតឱ្យចូលប្រើ​ម្ដង"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"មិនអនុញ្ញាត"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ចង់​បង្ហាញ​ស្ថិតិ​ប្រើប្រាស់​របស់ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"កែ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 7fd8aef..ac8fb7e 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"ಎಲ್ಲಾ ಸಾಧನದ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ಒಂದು ಬಾರಿಯ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ಅನುಮತಿಸಬೇಡಿ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು ಈಗಲೂ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು ಈಗಲೂ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು. ಹಾಗಿದ್ದರೂ, ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಬಹುದು.\n\ng.co/android/devicelogs ನಲ್ಲಿ ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು <xliff:g id="APP_0">%1$s</xliff:g> ತೋರಿಸಲು ಬಯಸಿದೆ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ಎಡಿಟ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 35e9e85..75bfbe4 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1162,8 +1162,8 @@
     <string name="no" msgid="5122037903299899715">"취소"</string>
     <string name="dialog_alert_title" msgid="651856561974090712">"주의"</string>
     <string name="loading" msgid="3138021523725055037">"로드 중.."</string>
-    <string name="capital_on" msgid="2770685323900821829">"ON"</string>
-    <string name="capital_off" msgid="7443704171014626777">"OFF"</string>
+    <string name="capital_on" msgid="2770685323900821829">"사용 설정"</string>
+    <string name="capital_off" msgid="7443704171014626777">"사용 안함"</string>
     <string name="checked" msgid="9179896827054513119">"선택함"</string>
     <string name="not_checked" msgid="7972320087569023342">"선택 안함"</string>
     <string name="selected" msgid="6614607926197755875">"선택됨"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>에서 모든 기기 로그에 액세스하도록 허용하시겠습니까?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"일회성 액세스 허용"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"허용 안함"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"다시 표시 안함"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하려고 합니다"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"수정"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 176a49d..68be904 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> колдонмосуна түзмөктөгү бардык таржымалдарды жеткиликтүү кыласызбы?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бир жолу жеткиликтүү кылуу"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Жок"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан түзмөктөгү бардык таржымалдарды ишенимдүү колдонмолорго гана пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан түзмөктөгү бардык таржымалдарды ишенимдүү колдонмолорго гана пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Экинчи көрүнбөсүн"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосу <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөткөнү жатат"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Түзөтүү"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 7ca4b56..5865c6e 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"ອະນຸຍາດໃຫ້ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດບໍ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ອະນຸຍາດການເຂົ້າເຖິງແບບເທື່ອດຽວ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ບໍ່ອະນຸຍາດ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້.\n\nສຶກສາເພີ່ມເຕີມໄດ້ຢູ່ g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ຕ້ອງການສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ແກ້ໄຂ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 9f646f0..a62ff4f 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Leisti „<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>“ pasiekti visus įrenginio žurnalus?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leisti vienkartinę prieigą"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neleisti"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Įrenginyje įrašoma, kas įvyksta jūsų įrenginyje. Programos gali naudoti šiuos žurnalus, kad surastų ir išspręstų problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Įrenginyje įrašoma, kas įvyksta jūsų įrenginyje. Programos gali naudoti šiuos žurnalus, kad surastų ir išspręstų problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daugiau neberodyti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"„<xliff:g id="APP_0">%1$s</xliff:g>“ nori rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redaguoti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3a078db..172e8a5 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2051,7 +2051,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vai atļaujat lietotnei <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> piekļūt visiem ierīces žurnāliem?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Atļaut vienreizēju piekļuvi"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neatļaut"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vairs nerādīt"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Lietotne <xliff:g id="APP_0">%1$s</xliff:g> vēlas rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Rediģēt"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index c67819e..b4863df 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се дозволи <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да пристапува до целата евиденција на уредот?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи еднократен пристап"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволувај"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот.\n\nДознајте повеќе на g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не прикажувај повторно"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> сака да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 63a5e0d..14a73fd 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"എല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാൻ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ഒറ്റത്തവണ ആക്‌സസ് അനുവദിക്കുക"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"അനുവദിക്കരുത്"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും.\n\ng.co/android/devicelogs എന്നതിൽ കൂടുതലറിയുക."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"വീണ്ടും കാണിക്കരുത്"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g> താൽപ്പര്യപ്പെടുന്നു"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"എഡിറ്റ് ചെയ്യുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 9be2b2c..cdf68a6 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-д төхөөрөмжийн бүх логт хандахыг зөвшөөрөх үү?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Нэг удаагийн хандалтыг зөвшөөрнө үү"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Бүү зөвшөөр"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Tаны төхөөрөмж үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Tаны төхөөрөмж үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Таны төхөөрөмжийн үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй.\n\ng.co/android/devicelogs -с нэмэлт мэдээлэл аваарай."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Дахиж бүү харуул"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулах хүсэлтэй байна"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Засах"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 5729355b..ff8d82f 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक वेळ अ‍ॅक्सेसची अनुमती द्या"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमती देऊ नका"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो.\n\ng.co/android/devicelogs येथे अधिक जाणून घ्या."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"पुन्हा दाखवू नका"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवायचे आहेत"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"संपादित करा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3595acc..8566afb 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Benarkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log peranti?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Benarkan akses satu kali"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan benarkan"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl dapat menggunakan log ini untuk menemukan dan membetulkan isu.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi benarkan apl yang anda percaya sahaja untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl dapat menggunakan log ini untuk menemukan dan membetulkan isu.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi benarkan apl yang anda percaya sahaja untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl boleh menggunakan log ini untuk menemukan dan membetulkan masalah.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi hanya benarkan apl yang anda percaya untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl ini masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda.\n\nKetahui lebih lanjut di g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tunjuk lagi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> mahu menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index da34c95..5a9f2ac 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုမလား။"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"တစ်ခါသုံး ဝင်ခွင့်ပေးရန်"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ခွင့်မပြုပါ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် စက်မှတ်တမ်းအားလုံးကို ယုံကြည်ရသည့် အက်ပ်များကိုသာ သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ၎င်း၏ကိုယ်ပိုင်မှတ်တမ်းကို သုံးနိုင်ဆဲဖြစ်သည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် စက်မှတ်တမ်းအားလုံးကို ယုံကြည်ရသည့် အက်ပ်များကိုသာ သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ၎င်း၏ကိုယ်ပိုင်မှတ်တမ်းကို သုံးနိုင်ဆဲဖြစ်သည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"နောက်ထပ်မပြပါနှင့်"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> သည် <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များကို ပြသလိုသည်"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"တည်းဖြတ်ရန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 87e206d..0cf718b 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du gi <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tilgang til alle enhetslogger?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gi éngangstilgang"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ikke tillat"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Enhetslogger registrerer det som skjer på enheten din. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten din."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhetslogger registrerer det som skjer på enheten din. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten din."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetslogger registrerer det som skjer på enheten. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten.\n\nFinn ut mer på g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ikke vis igjen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vil vise <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Endre"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 5df7009..4ebbe74 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> लाई डिभाइसका सबै लग हेर्ने अनुमति दिने हो?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक पटक प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति नदिनुहोस्"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फेरि नदेखाइयोस्"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ले <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन चाहन्छ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"सम्पादन गर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f89bc78..583b2de 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang geven tot alle apparaatlogboeken?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eenmalige toegang toestaan"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Niet toestaan"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat.\n\nGa voor meer informatie naar g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Niet opnieuw tonen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil segmenten van <xliff:g id="APP_2">%2$s</xliff:g> tonen"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Bewerken"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 9b38ea8..d7c1f51 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ଗୋଟିଏ-ଥର ଆକ୍ସେସ ପାଇଁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।\n\ng.co/android/devicelogsରେ ଅଧିକ ଜାଣନ୍ତୁ।"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍‌କୁ ଦେଖାଇବା ପାଇଁ ଚାହେଁ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ଏଡିଟ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index ed8278b..3e12790 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"ਕੀ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ਇੱਕ-ਵਾਰ ਲਈ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।\n\ng.co/android/devicelogs \'ਤੇ ਹੋਰ ਜਾਣੋ।"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ਦੀ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੀ ਇੱਛਾ ਹੈ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ਸੰਪਾਦਨ ਕਰੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index a3fbbad..43b520e 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -2052,7 +2052,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Zezwolić aplikacji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na dostęp do wszystkich dzienników urządzenia?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Zezwól na jednorazowy dostęp"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nie zezwalaj"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu.\n\nWięcej informacji znajdziesz na stronie g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nie pokazuj ponownie"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacja <xliff:g id="APP_0">%1$s</xliff:g> chce pokazywać wycinki z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edytuj"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 55f5bcf..a4f7da2 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 2938b96..08b3302 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -181,7 +181,7 @@
     <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Pelo gestor do seu perfil de trabalho"</string>
     <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Por <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
     <string name="work_profile_deleted" msgid="5891181538182009328">"Perfil de trabalho eliminado"</string>
-    <string name="work_profile_deleted_details" msgid="3773706828364418016">"A app de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o gestor para obter assistência."</string>
+    <string name="work_profile_deleted_details" msgid="3773706828364418016">"A app de administração do perfil de trabalho está em falta ou danificada. Por isso, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o gestor para obter assistência."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"O seu perfil de trabalho já não está disponível neste dispositivo"</string>
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiadas tentativas de introdução da palavra-passe"</string>
     <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador anulou o dispositivo para utilização pessoal."</string>
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que a app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aceda a todos os registos do dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acesso único"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo deve apenas ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo deve apenas ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo só deve ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo.\n\nSaiba mais em g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar de novo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"A app <xliff:g id="APP_0">%1$s</xliff:g> pretende mostrar partes da app <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 55f5bcf..a4f7da2 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 7c34345..98f8ff2 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -29,7 +29,7 @@
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problemă de conexiune sau cod MMI nevalid."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operația este limitată la numerele cu apelări restricționate."</string>
-    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu puteți schimba setările de redirecționare a apelurilor de pe telefon când sunteți în roaming."</string>
+    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu poți schimba setările de redirecționare a apelurilor de pe telefon când ești în roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Serviciul a fost activat."</string>
     <string name="serviceEnabledFor" msgid="1463104778656711613">"Serviciul a fost activat pentru:"</string>
     <string name="serviceDisabled" msgid="641878791205871379">"Serviciul a fost dezactivat."</string>
@@ -39,16 +39,16 @@
     <string name="mmiComplete" msgid="6341884570892520140">"MMI finalizat."</string>
     <string name="badPin" msgid="888372071306274355">"Codul PIN vechi introdus nu este corect."</string>
     <string name="badPuk" msgid="4232069163733147376">"Codul PUK introdus nu este corect."</string>
-    <string name="mismatchPin" msgid="2929611853228707473">"Codurile PIN introduse nu se potrivesc."</string>
+    <string name="mismatchPin" msgid="2929611853228707473">"PIN-urile introduse nu sunt identice."</string>
     <string name="invalidPin" msgid="7542498253319440408">"Introdu un cod PIN alcătuit din 4 până la 8 cifre."</string>
     <string name="invalidPuk" msgid="8831151490931907083">"Introdu un cod PUK care să aibă 8 cifre sau mai mult."</string>
     <string name="needPuk" msgid="7321876090152422918">"Cardul SIM este blocat cu codul PUK. Introdu codul PUK pentru a-l debloca."</string>
     <string name="needPuk2" msgid="7032612093451537186">"Introdu codul PUK2 pentru a debloca cardul SIM."</string>
-    <string name="enablePin" msgid="2543771964137091212">"Operațiunea nu a reușit. Activați blocarea cardului SIM/RUIM."</string>
+    <string name="enablePin" msgid="2543771964137091212">"Operațiunea nu a reușit. Activează blocarea cardului SIM/RUIM."</string>
     <plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
-      <item quantity="few">V-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> încercări până la blocarea cardului SIM.</item>
-      <item quantity="other">V-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> de încercări până la blocarea cardului SIM.</item>
-      <item quantity="one">V-a mai rămas <xliff:g id="NUMBER_0">%d</xliff:g> încercare până la blocarea cardului SIM.</item>
+      <item quantity="few">Ți-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> încercări până la blocarea cardului SIM.</item>
+      <item quantity="other">Ți-au mai rămas <xliff:g id="NUMBER_1">%d</xliff:g> de încercări până la blocarea cardului SIM.</item>
+      <item quantity="one">Ți-a mai rămas <xliff:g id="NUMBER_0">%d</xliff:g> încercare până la blocarea cardului SIM.</item>
     </plurals>
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
@@ -66,13 +66,13 @@
     <string name="ThreeWCMmi" msgid="2436550866139999411">"Apelare de tip conferință"</string>
     <string name="RuacMmi" msgid="1876047385848991110">"Respingere apeluri supărătoare nedorite"</string>
     <string name="CndMmi" msgid="185136449405618437">"Se apelează serviciul de furnizare a numerelor"</string>
-    <string name="DndMmi" msgid="8797375819689129800">"Nu deranjați"</string>
+    <string name="DndMmi" msgid="8797375819689129800">"Nu deranja"</string>
     <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: restricționat"</string>
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: Restricționat."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string>
-    <string name="CLIRPermanent" msgid="166443681876381118">"Nu puteți să modificați setarea pentru ID-ul apelantului."</string>
+    <string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Fără serviciu de date mobile"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Apelurile de urgență nu sunt disponibile"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Fără servicii vocale"</string>
@@ -80,9 +80,9 @@
     <string name="RestrictedStateContent" msgid="7693575344608618926">"Dezactivat temporar de operator"</string>
     <string name="RestrictedStateContentMsimTemplate" msgid="5228235722511044687">"Dezactivat temporar de operator pentru numărul de card SIM <xliff:g id="SIMNUMBER">%d</xliff:g>"</string>
     <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"Nu se poate stabili conexiunea la rețeaua mobilă"</string>
-    <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"Încercați să schimbați rețeaua preferată. Atingeți pentru a schimba."</string>
+    <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"Încearcă să schimbi rețeaua preferată. Atinge pentru a schimba."</string>
     <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"Apelurile de urgență nu sunt disponibile"</string>
-    <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"Nu puteți efectua apeluri de urgență prin Wi-Fi"</string>
+    <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"Nu poți face apeluri de urgență prin Wi-Fi"</string>
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"Alerte"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"Redirecționarea apelurilor"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Mod de apelare inversă de urgență"</string>
@@ -120,10 +120,10 @@
     <string name="roamingTextSearching" msgid="5323235489657753486">"Se caută serviciul"</string>
     <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Nu s-a putut configura apelarea prin Wi-Fi"</string>
   <string-array name="wfcOperatorErrorAlertMessages">
-    <item msgid="468830943567116703">"Pentru a efectua apeluri și a trimite mesaje prin Wi-Fi, mai întâi solicitați configurarea acestui serviciu la operator. Apoi, activați din nou apelarea prin Wi-Fi din Setări. (Cod de eroare: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
+    <item msgid="468830943567116703">"Pentru a face apeluri și a trimite mesaje prin Wi-Fi, mai întâi solicită configurarea acestui serviciu la operator. Apoi, activează din nou apelarea prin Wi-Fi din Setări. (Cod de eroare: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
   </string-array>
   <string-array name="wfcOperatorErrorNotificationMessages">
-    <item msgid="4795145070505729156">"A apărut o problemă la înregistrarea apelării prin Wi‑Fi la operatorul dvs.: <xliff:g id="CODE">%1$s</xliff:g>"</item>
+    <item msgid="4795145070505729156">"A apărut o problemă la înregistrarea apelării prin Wi‑Fi la operatorul tău: <xliff:g id="CODE">%1$s</xliff:g>"</item>
   </string-array>
     <!-- no translation found for wfcSpnFormat_spn (2982505428519096311) -->
     <skip />
@@ -139,8 +139,8 @@
     <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Apelare prin Wi-Fi"</string>
     <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
     <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Dezactivată"</string>
-    <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Apelați prin Wi-Fi"</string>
-    <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Apelați prin rețeaua mobilă"</string>
+    <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Apelează prin Wi-Fi"</string>
+    <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Sună prin rețeaua mobilă"</string>
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
@@ -171,24 +171,24 @@
     <string name="notification_title" msgid="5783748077084481121">"Eroare de conectare pentru <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
     <string name="contentServiceSync" msgid="2341041749565687871">"Sincronizare"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Nu se poate sincroniza"</string>
-    <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Ați încercat să ștergeți prea multe <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Ai încercat să ștergi prea multe <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
     <string name="low_memory" product="tablet" msgid="5557552311566179924">"Stocarea pe tabletă este plină. Șterge câteva fișiere pentru a elibera spațiu."</string>
     <string name="low_memory" product="watch" msgid="3479447988234030194">"Spațiul de stocare de pe ceas este plin! Șterge câteva fișiere pentru a elibera spațiu."</string>
     <string name="low_memory" product="tv" msgid="6663680413790323318">"Spațiul de stocare de pe dispozitivul Android TV este plin. Șterge câteva fișiere pentru a elibera spațiu."</string>
     <string name="low_memory" product="default" msgid="2539532364144025569">"Stocarea pe telefon este plină. Șterge câteva fișiere pentru a elibera spațiu."</string>
     <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{S-a instalat o autoritate de certificare}few{S-au instalat autorități de certificare}other{S-au instalat autorități de certificare}}"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"De o terță parte necunoscută"</string>
-    <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"De administratorul profilului dvs. de serviciu"</string>
+    <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"De administratorul profilului de serviciu"</string>
     <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"De <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
     <string name="work_profile_deleted" msgid="5891181538182009328">"Profilul de serviciu a fost șters"</string>
-    <string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplicația de administrare a profilului de serviciu lipsește sau este deteriorată. Prin urmare, profilul de serviciu și datele asociate au fost șterse. Pentru asistență, contactați administratorul."</string>
+    <string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplicația de administrare a profilului de serviciu lipsește sau este deteriorată. Prin urmare, profilul de serviciu și datele asociate au fost șterse. Pentru asistență, contactează administratorul."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profilul de serviciu nu mai este disponibil pe acest dispozitiv"</string>
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Prea multe încercări de introducere a parolei"</string>
     <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratorul a retras dispozitivul pentru uz personal"</string>
     <string name="network_logging_notification_title" msgid="554983187553845004">"Dispozitivul este gestionat"</string>
-    <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizația dvs. gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atingeți pentru mai multe detalii."</string>
-    <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplicațiile vă pot accesa locația"</string>
-    <string name="location_changed_notification_text" msgid="7158423339982706912">"Contactați administratorul IT pentru a afla mai multe"</string>
+    <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizația ta gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atinge pentru mai multe detalii."</string>
+    <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplicațiile îți pot accesa locația"</string>
+    <string name="location_changed_notification_text" msgid="7158423339982706912">"Contactează administratorul IT pentru a afla mai multe"</string>
     <string name="geofencing_service" msgid="3826902410740315456">"Serviciul de delimitare geografică"</string>
     <string name="country_detector" msgid="7023275114706088854">"Detector de țară"</string>
     <string name="location_service" msgid="2439187616018455546">"Servicii de localizare"</string>
@@ -199,20 +199,20 @@
     <string name="device_policy_manager_service" msgid="5085762851388850332">"Serviciul Manager de politici pentru dispozitive"</string>
     <string name="music_recognition_manager_service" msgid="7481956037950276359">"Serviciu de gestionare a recunoașterii de melodii"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Datele de pe dispozitiv vor fi șterse"</string>
-    <string name="factory_reset_message" msgid="2657049595153992213">"Aplicația de administrare nu poate fi utilizată. Dispozitivul va fi șters.\n\nDacă aveți întrebări, contactați administratorul organizației dvs."</string>
+    <string name="factory_reset_message" msgid="2657049595153992213">"Aplicația de administrare nu poate fi folosită. Dispozitivul va fi șters.\n\nDacă ai întrebări, contactează administratorul organizației."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Printare dezactivată de <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
-    <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Activați profilul de serviciu"</string>
-    <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Aplicațiile personale sunt blocate până când activați profilul de serviciu"</string>
+    <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Activează profilul de serviciu"</string>
+    <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Aplicațiile personale sunt blocate până când activezi profilul de serviciu"</string>
     <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"Aplicațiile personale vor fi blocate pe <xliff:g id="DATE">%1$s</xliff:g>, la <xliff:g id="TIME">%2$s</xliff:g>. Administratorul IT nu permite ca profilul de serviciu să fie dezactivat mai mult de <xliff:g id="NUMBER">%3$d</xliff:g> zile."</string>
-    <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Activați"</string>
+    <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Activează"</string>
     <string name="me" msgid="6207584824693813140">"Eu"</string>
     <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Opțiuni tablet PC"</string>
     <string name="power_dialog" product="tv" msgid="7792839006640933763">"Opțiuni pentru Android TV"</string>
     <string name="power_dialog" product="default" msgid="1107775420270203046">"Opțiuni telefon"</string>
     <string name="silent_mode" msgid="8796112363642579333">"Mod Silențios"</string>
-    <string name="turn_on_radio" msgid="2961717788170634233">"Activați funcția wireless"</string>
-    <string name="turn_off_radio" msgid="7222573978109933360">"Dezactivați funcția wireless"</string>
-    <string name="screen_lock" msgid="2072642720826409809">"Blocați ecranul"</string>
+    <string name="turn_on_radio" msgid="2961717788170634233">"Activează funcția wireless"</string>
+    <string name="turn_off_radio" msgid="7222573978109933360">"Dezactivează funcția wireless"</string>
+    <string name="screen_lock" msgid="2072642720826409809">"Blochează ecranul"</string>
     <string name="power_off" msgid="4111692782492232778">"Oprește"</string>
     <string name="silent_mode_silent" msgid="5079789070221150912">"Sonerie dezactivată"</string>
     <string name="silent_mode_vibrate" msgid="8821830448369552678">"Vibrare sonerie"</string>
@@ -224,32 +224,32 @@
     <string name="reboot_to_reset_title" msgid="2226229680017882787">"Revenire la setările din fabrică"</string>
     <string name="reboot_to_reset_message" msgid="3347690497972074356">"Se repornește…"</string>
     <string name="shutdown_progress" msgid="5017145516412657345">"Se închide..."</string>
-    <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Computerul dvs. tablet PC se va închide."</string>
+    <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Tableta se va închide."</string>
     <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Dispozitivul Android TV se va închide."</string>
-    <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Ceasul dvs. se va închide."</string>
-    <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefonul dvs. se va închide."</string>
-    <string name="shutdown_confirm_question" msgid="796151167261608447">"Doriți să închideți?"</string>
+    <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Ceasul se va închide."</string>
+    <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefonul se va închide."</string>
+    <string name="shutdown_confirm_question" msgid="796151167261608447">"Vrei să închizi?"</string>
     <string name="reboot_safemode_title" msgid="5853949122655346734">"Repornește în modul sigur"</string>
-    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Doriți să reporniți în modul sigur? Astfel vor fi dezactivate toate aplicațiile terță parte pe care le-ați instalat. Acestea vor fi restabilite când reporniți din nou."</string>
+    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Repornești în modul sigur? Astfel vor fi dezactivate toate aplicațiile terță parte instalate. Acestea vor fi restabilite când repornești dispozitivul."</string>
     <string name="recent_tasks_title" msgid="8183172372995396653">"Recente"</string>
     <string name="no_recent_tasks" msgid="9063946524312275906">"Nu există aplicații recente."</string>
     <string name="global_actions" product="tablet" msgid="4412132498517933867">"Opțiuni tablet PC"</string>
     <string name="global_actions" product="tv" msgid="3871763739487450369">"Opțiuni pentru Android TV"</string>
     <string name="global_actions" product="default" msgid="6410072189971495460">"Opțiuni telefon"</string>
-    <string name="global_action_lock" msgid="6949357274257655383">"Blocați ecranul"</string>
+    <string name="global_action_lock" msgid="6949357274257655383">"Blochează ecranul"</string>
     <string name="global_action_power_off" msgid="4404936470711393203">"Oprește"</string>
     <string name="global_action_power_options" msgid="1185286119330160073">"Alimentare"</string>
     <string name="global_action_restart" msgid="4678451019561687074">"Repornește"</string>
     <string name="global_action_emergency" msgid="1387617624177105088">"Urgență"</string>
     <string name="global_action_bug_report" msgid="5127867163044170003">"Raport despre erori"</string>
-    <string name="global_action_logout" msgid="6093581310002476511">"Încheiați sesiunea"</string>
+    <string name="global_action_logout" msgid="6093581310002476511">"Încheie sesiunea"</string>
     <string name="global_action_screenshot" msgid="2610053466156478564">"Instantaneu"</string>
     <string name="bugreport_title" msgid="8549990811777373050">"Raport de eroare"</string>
-    <string name="bugreport_message" msgid="5212529146119624326">"Acest raport va colecta informații despre starea actuală a dispozitivului, pentru a le trimite într-un e-mail. Aveți răbdare după pornirea raportului despre erori până când va fi gata de trimis."</string>
+    <string name="bugreport_message" msgid="5212529146119624326">"Acest raport va colecta informații despre starea actuală a dispozitivului, pentru a le trimite într-un e-mail. Ai răbdare după pornirea raportului despre erori până când va fi gata de trimis."</string>
     <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Raport interactiv"</string>
-    <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Folosiți această opțiune în majoritatea situațiilor. Astfel, puteți să urmăriți progresul raportului, să introduceți mai multe detalii în privința problemei și să creați capturi de ecran. Pot fi omise unele secțiuni mai puțin folosite pentru care raportarea durează prea mult."</string>
+    <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Folosește această opțiune în majoritatea situațiilor. Astfel, poți să urmărești progresul raportului, să introduci mai multe detalii în privința problemei și să creezi capturi de ecran. Pot fi omise unele secțiuni mai puțin folosite pentru care raportarea durează prea mult."</string>
     <string name="bugreport_option_full_title" msgid="7681035745950045690">"Raport complet"</string>
-    <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Folosiți această opțiune pentru a reduce la minimum interferențele cu sistemul când dispozitivul nu răspunde, funcționează prea lent sau când aveți nevoie de toate secțiunile raportului. Nu puteți să introduceți mai multe detalii sau să creați capturi de ecran suplimentare."</string>
+    <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Folosește această opțiune pentru a reduce la minimum interferențele cu sistemul când dispozitivul nu răspunde, funcționează prea lent sau când ai nevoie de toate secțiunile raportului. Nu poți să introduci mai multe detalii sau să creezi capturi de ecran suplimentare."</string>
     <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Peste # secundă se va realiza o captură de ecran pentru raportul de eroare.}few{Peste # secunde se va realiza o captură de ecran pentru raportul de eroare.}other{Peste # de secunde se va realiza o captură de ecran pentru raportul de eroare.}}"</string>
     <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"S-a realizat captura de ecran a raportului de eroare"</string>
     <string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Nu s-a realizat captura de ecran a raportului de eroare"</string>
@@ -291,16 +291,16 @@
     <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="8974401416068943888">"Mod sigur"</string>
     <string name="android_system_label" msgid="5974767339591067210">"Sistemul Android"</string>
-    <string name="user_owner_label" msgid="8628726904184471211">"Comutați la profilul personal"</string>
-    <string name="managed_profile_label" msgid="7316778766973512382">"Comutați la profilul de serviciu"</string>
+    <string name="user_owner_label" msgid="8628726904184471211">"Comută la profilul personal"</string>
+    <string name="managed_profile_label" msgid="7316778766973512382">"Comută la profilul de serviciu"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Agendă"</string>
-    <string name="permgroupdesc_contacts" msgid="9163927941244182567">"acceseze persoanele de contact"</string>
+    <string name="permgroupdesc_contacts" msgid="9163927941244182567">"să acceseze agenda"</string>
     <string name="permgrouplab_location" msgid="1858277002233964394">"Locație"</string>
-    <string name="permgroupdesc_location" msgid="1995955142118450685">"acceseze locația acestui dispozitiv"</string>
+    <string name="permgroupdesc_location" msgid="1995955142118450685">"să acceseze locația acestui dispozitiv"</string>
     <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string>
-    <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceseze calendarul"</string>
+    <string name="permgroupdesc_calendar" msgid="6762751063361489379">"să acceseze calendarul"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
-    <string name="permgroupdesc_sms" msgid="5726462398070064542">"trimită și să vadă mesajele SMS"</string>
+    <string name="permgroupdesc_sms" msgid="5726462398070064542">"să trimită și să vadă mesajele SMS"</string>
     <string name="permgrouplab_storage" msgid="17339216290379241">"Fișiere"</string>
     <string name="permgroupdesc_storage" msgid="5378659041354582769">"să acceseze fișiere de pe dispozitiv"</string>
     <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzică și conținut audio"</string>
@@ -310,32 +310,32 @@
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"înregistreze sunet"</string>
     <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Activitate fizică"</string>
-    <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"accesați activitatea fizică"</string>
+    <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"să acceseze activitatea fizică"</string>
     <string name="permgrouplab_camera" msgid="9090413408963547706">"Camera foto"</string>
     <string name="permgroupdesc_camera" msgid="7585150538459320326">"fotografieze și să înregistreze videoclipuri"</string>
     <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Dispozitive din apropiere"</string>
-    <string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"descoperiți dispozitive din apropiere și conectați-vă la acestea"</string>
+    <string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"descoperă dispozitive din apropiere și conectează-te la acestea"</string>
     <string name="permgrouplab_calllog" msgid="7926834372073550288">"Jurnale de apeluri"</string>
     <string name="permgroupdesc_calllog" msgid="2026996642917801803">"să citească și să scrie jurnalul de apeluri telefonice"</string>
     <string name="permgrouplab_phone" msgid="570318944091926620">"Telefon"</string>
-    <string name="permgroupdesc_phone" msgid="270048070781478204">"inițieze și să gestioneze apeluri telefonice"</string>
+    <string name="permgroupdesc_phone" msgid="270048070781478204">"să inițieze și să gestioneze apeluri telefonice"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Senzori corporali"</string>
-    <string name="permgroupdesc_sensors" msgid="2610631290633747752">"acceseze datele de la senzori despre semnele vitale"</string>
+    <string name="permgroupdesc_sensors" msgid="2610631290633747752">"să acceseze datele de la senzori despre semnele vitale"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notificări"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"să afișeze notificări"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Analizeze conținutul ferestrei"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspectează conținutul unei ferestre cu care interacționați."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activeze funcția Explorați prin atingere"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"să preia conținutul ferestrei"</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspectează conținutul unei ferestre cu care interacționezi."</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"să activeze funcția Explorează prin atingere"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Elementele atinse vor fi rostite cu voce tare, iar ecranul poate fi explorat utilizând gesturi."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Remarce textul pe care îl introduceți"</string>
-    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Include date personale, cum ar fi numere ale cardurilor de credit sau parole."</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"să vadă textul pe care îl introduci"</string>
+    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Include date cu caracter personal, cum ar fi numere ale cardurilor de credit sau parole."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Controlează mărirea pe afișaj"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controlează nivelul de zoom și poziționarea afișajului."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Folosește gesturi"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Poate atinge, glisa, ciupi sau folosi alte gesturi."</string>
-    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Redea gesturi ce implică amprente"</string>
+    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"să redea gesturi ce implică amprente"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Poate reda gesturile făcute pe senzorul de amprentă al dispozitivului."</string>
-    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Faceți o captură de ecran"</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fă o captură de ecran"</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Poate face o captură de ecran."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"dezactivare sau modificare bare de stare"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite aplicației să dezactiveze bara de stare sau să adauge și să elimine pictograme de sistem."</string>
@@ -354,34 +354,34 @@
     <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"să răspundă la apeluri telefonice"</string>
     <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Permite aplicației să răspundă la un apel telefonic."</string>
     <string name="permlab_receiveSms" msgid="505961632050451881">"primește mesaje text (SMS)"</string>
-    <string name="permdesc_receiveSms" msgid="1797345626687832285">"Permite aplicației să primească și să proceseze mesaje SMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string>
+    <string name="permdesc_receiveSms" msgid="1797345626687832285">"Permite aplicației să primească și să proceseze mesaje SMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitiv fără a ți le arăta."</string>
     <string name="permlab_receiveMms" msgid="4000650116674380275">"primește mesaje text (MMS)"</string>
-    <string name="permdesc_receiveMms" msgid="958102423732219710">"Permite aplicației să primească și să proceseze mesaje MMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string>
+    <string name="permdesc_receiveMms" msgid="958102423732219710">"Permite aplicației să primească și să proceseze mesaje MMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitiv fără a ți le arăta."</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Redirecționează mesajele cu transmisie celulară"</string>
-    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite aplicației să se conecteze la modulul de transmisie celulară pentru a redirecționa mesajele cu transmisie celulară pe măsură ce le primește. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgență."</string>
+    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite aplicației să se conecteze la modulul de transmisie celulară pentru a redirecționa mesajele cu transmisie celulară pe măsură ce le primește. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a te avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului când e primită o transmisie celulară de urgență."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Să gestioneze apelurile în desfășurare"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite unei aplicații să vadă detalii despre apelurile în desfășurare de pe dispozitiv și să gestioneze apelurile respective."</string>
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"citește mesajele cu transmisie celulară"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitivul dvs. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgență."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitiv. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a te avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului când e primită o transmisie celulară de urgență."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"citire feeduri abonat"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Permite aplicației să obțină detalii despre feedurile sincronizate în prezent."</string>
-    <string name="permlab_sendSms" msgid="7757368721742014252">"trimită și să vadă mesajele SMS"</string>
-    <string name="permdesc_sendSms" msgid="6757089798435130769">"Permite aplicației să trimită mesaje SMS, ceea ce ar putea determina apariția unor taxe neașteptate. Aplicațiile rău intenționate pot acumula costuri prin trimiterea mesajelor fără confirmarea dvs."</string>
+    <string name="permlab_sendSms" msgid="7757368721742014252">"să trimită și să vadă mesajele SMS"</string>
+    <string name="permdesc_sendSms" msgid="6757089798435130769">"Permite aplicației să trimită mesaje SMS, ceea ce ar putea duce la costuri neașteptate. Aplicațiile rău intenționate pot acumula costuri prin trimiterea mesajelor fără confirmarea ta."</string>
     <string name="permlab_readSms" msgid="5164176626258800297">"citește mesajele text (SMS sau MMS)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Această aplicație poate citi toate mesajele SMS stocate pe tabletă."</string>
     <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Această aplicație poate să citească toate mesajele SMS (texT) stocate pe dispozitivul Android TV."</string>
     <string name="permdesc_readSms" product="default" msgid="774753371111699782">"Această aplicație poate citi toate mesajele SMS stocate pe telefon."</string>
     <string name="permlab_receiveWapPush" msgid="4223747702856929056">"primește mesaje text (WAP)"</string>
-    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Permite aplicației să primească și să proceseze mesaje WAP. Această permisiune include capacitatea de a monitoriza sau șterge mesajele care v-au fost trimise fără a vi le arăta."</string>
+    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Permite aplicației să primească și să proceseze mesaje WAP. Această permisiune include capacitatea de a monitoriza sau șterge mesajele care ți-au fost trimise fără a ți le arăta."</string>
     <string name="permlab_getTasks" msgid="7460048811831750262">"preluare aplicații care rulează"</string>
     <string name="permdesc_getTasks" msgid="7388138607018233726">"Permite aplicației să preia informațiile despre activitățile care rulează în prezent și care au rulat recent. În acest fel, aplicația poate descoperi informații despre aplicațiile care sunt utilizate pe dispozitiv."</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"să gestioneze profilul și proprietarii dispozitivului"</string>
     <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Permite aplicațiilor să seteze proprietarii de profiluri și proprietarul dispozitivului."</string>
     <string name="permlab_reorderTasks" msgid="7598562301992923804">"reordonare aplicații care rulează"</string>
-    <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Permite aplicației să mute activitățile în prim-plan și în fundal. Aplicația poate face acest lucru fără aportul dvs."</string>
+    <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Permite aplicației să mute activitățile în prim-plan și în fundal. Aplicația poate face acest lucru fără intervenția ta."</string>
     <string name="permlab_enableCarMode" msgid="893019409519325311">"activare mod Mașină"</string>
     <string name="permdesc_enableCarMode" msgid="56419168820473508">"Permite aplicației să activeze modul Mașină."</string>
-    <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"închide alte aplicații"</string>
+    <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"să închidă alte aplicații"</string>
     <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Permite aplicației să oprească procesele derulate în fundal de alte aplicații. Acest lucru poate face ca respectivele aplicații să nu mai ruleze."</string>
     <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Această aplicație poate apărea deasupra altor aplicații"</string>
     <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Această aplicație poate apărea deasupra altor aplicații sau a altor părți ale ecranului. Acest lucru poate să afecteze utilizarea normală a aplicației și să schimbe modul în care se afișează alte aplicații."</string>
@@ -398,7 +398,7 @@
     <string name="permlab_getPackageSize" msgid="375391550792886641">"măsurare spațiu de stocare al aplicației"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite aplicației să preia dimensiunile codului, ale datelor și ale memoriei cache"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifică setări de sistem"</string>
-    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Permite aplicației să modifice datele din setările sistemului. Aplicațiile rău intenționate pot corupe configurația sistemului dvs."</string>
+    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Permite aplicației să modifice datele din setările sistemului. Aplicațiile rău intenționate pot corupe configurația sistemului."</string>
     <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"rulează la pornire"</string>
     <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Permite aplicației să pornească imediat ce s-a terminat încărcarea sistemului. Din acest motiv, pornirea tabletei poate dura mai mult timp, iar rularea continuă a aplicației poate încetini dispozitivul."</string>
     <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Permite aplicației să pornească imediat ce s-a terminat încărcarea sistemului. Din acest motiv, pornirea dispozitivului Android TV poate dura mai mult timp, iar rularea continuă a aplicației poate încetini dispozitivul."</string>
@@ -408,9 +408,9 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite aplicației să trimită mesaje difuzate persistente, care se păstrează după terminarea difuzării mesajului. Utilizarea excesivă a acestei funcții poate să încetinească sau să destabilizeze dispozitivul Android TV, determinându-l să utilizeze prea multă memorie."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite aplicației să trimită mesaje difuzate persistente, care se păstrează după terminarea difuzării mesajului. Utilizarea excesivă a acestei funcții poate să încetinească sau să destabilizeze telefonul, determinându-l să utilizeze prea multă memorie."</string>
     <string name="permlab_readContacts" msgid="8776395111787429099">"citește agenda"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite aplicației să citească datele despre persoanele din agenda stocată pe tabletă. Aplicațiile vor avea și acces la conturile de pe tabletă care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite aplicației să citească datele despre persoanele de contact din agenda stocată pe dispozitivul Android TV. Aplicațiile vor avea și acces la conturile de pe dispozitivul Android TV care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
-    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite aplicației să citească datele despre persoanele de contact salvate pe telefon. Aplicațiile vor avea și acces la conturile de pe telefon care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite aplicației să citească datele despre persoanele din agenda stocată pe tabletă. Aplicațiile vor avea și acces la conturile de pe tabletă care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ai instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău intenționate pot permite accesul la datele de contact fără cunoștința ta."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite aplicației să citească datele despre persoanele de contact din agenda stocată pe dispozitivul Android TV. Aplicațiile vor avea și acces la conturile de pe dispozitivul Android TV care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ai instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău intenționate pot permite accesul la datele de contact fără cunoștința ta."</string>
+    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite aplicației să citească datele despre persoanele de contact salvate pe telefon. Aplicațiile vor avea și acces la conturile de pe telefon care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ai instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău intenționate pot permite accesul la datele de contact fără cunoștința ta."</string>
     <string name="permlab_writeContacts" msgid="8919430536404830430">"modifică agenda"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe tabletă. Cu această permisiune, aplicația poate șterge datele de contact."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe dispozitivul Android TV. Cu această permisiune, aplicația poate șterge datele de contact."</string>
@@ -418,9 +418,9 @@
     <string name="permlab_readCallLog" msgid="1739990210293505948">"citește jurnalul de apeluri"</string>
     <string name="permdesc_readCallLog" msgid="8964770895425873433">"Această aplicație poate citi istoricul apelurilor."</string>
     <string name="permlab_writeCallLog" msgid="670292975137658895">"scrie jurnalul de apeluri"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Permite aplicației să modifice jurnalul de apeluri al tabletei dvs., inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul dvs. de apeluri."</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Permite aplicației să modifice jurnalul de apeluri al tabletei, inclusiv datele despre apelurile primite sau făcute. Aplicațiile rău intenționate pot folosi această permisiune pentru a șterge sau a modifica jurnalul de apeluri."</string>
     <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Permite aplicației să modifice jurnalul de apeluri al dispozitivului Android TV, inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul de apeluri."</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Permite aplicației să modifice jurnalul de apeluri al telefonului dvs., inclusiv datele despre apelurile primite sau efectuate. Aplicațiile rău intenționate pot utiliza această permisiune pentru a șterge sau pentru a modifica jurnalul dvs. de apeluri."</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Permite aplicației să modifice jurnalul de apeluri al telefonului, inclusiv datele despre apelurile primite sau făcute. Aplicațiile rău intenționate pot folosi această permisiune pentru a șterge sau a modifica jurnalul de apeluri."</string>
     <string name="permlab_bodySensors" msgid="662918578601619569">"Să acceseze date de la senzorii corporali, cum ar fi pulsul, în timpul folosirii"</string>
     <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Permite aplicației să acceseze date de la senzorii corporali, cum ar fi pulsul, temperatura și procentul de oxigen din sânge, în timpul folosirii aplicației."</string>
     <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Să acceseze date de la senzorii corporali, precum pulsul, când rulează în fundal"</string>
@@ -436,21 +436,21 @@
     <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"accesare comenzi suplimentare ale furnizorului locației"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite aplicației să acceseze comenzi suplimentare pentru furnizorul locației. Aplicația ar putea să utilizeze această permisiune pentru a influența operațiile GPS sau ale altor surse de locații."</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"să acceseze locația exactă în prim-plan"</string>
-    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Aplicația vă poate determina locația exactă cu ajutorul serviciilor de localizare atunci când este folosită. Pentru ca aplicația să poată determina locația, trebuie să activați serviciile de localizare pentru dispozitiv. Aceasta poate mări utilizarea bateriei."</string>
+    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Aplicația îți poate stabili locația exactă cu ajutorul serviciilor de localizare când este folosită. Pentru ca aplicația să poată stabili locația, trebuie să activezi serviciile de localizare pentru dispozitiv. Aceasta poate mări utilizarea bateriei."</string>
     <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"să acceseze locația aproximativă numai în prim-plan."</string>
-    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Aplicația vă poate determina locația aproximativă cu ajutorul serviciilor de localizare atunci când este folosită. Pentru ca aplicația să poată determina locația, trebuie să activați serviciile de localizare pentru dispozitiv."</string>
-    <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accesați locația în fundal"</string>
+    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Aplicația îți poate stabili locația aproximativă cu ajutorul serviciilor de localizare când este folosită. Pentru ca aplicația să poată stabili locația, trebuie să activezi serviciile de localizare pentru dispozitiv."</string>
+    <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"să acceseze locația în fundal"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Aplicația poate accesa locația oricând, chiar dacă nu este folosită."</string>
     <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modificare setări audio"</string>
     <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite aplicației să modifice setările audio globale, cum ar fi volumul și difuzorul care este utilizat pentru ieșire."</string>
-    <string name="permlab_recordAudio" msgid="1208457423054219147">"înregistreze sunet"</string>
+    <string name="permlab_recordAudio" msgid="1208457423054219147">"să înregistreze sunet"</string>
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Această aplicație poate să înregistreze conținut audio folosind microfonul când este în uz."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"să înregistreze conținut audio în fundal"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Această aplicație poate înregistra conținut audio folosind microfonul oricând."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"să trimită comenzi către SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recunoașterea activității fizice"</string>
-    <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Această aplicație vă poate recunoaște activitatea fizică."</string>
+    <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Această aplicație îți poate recunoaște activitatea fizică."</string>
     <string name="permlab_camera" msgid="6320282492904119413">"realizarea de fotografii și videoclipuri"</string>
     <string name="permdesc_camera" msgid="5240801376168647151">"Această aplicație poate să fotografieze și să înregistreze videoclipuri folosind camera foto când este în uz."</string>
     <string name="permlab_backgroundCamera" msgid="7549917926079731681">"să fotografieze și să înregistreze videoclipuri în fundal"</string>
@@ -462,17 +462,17 @@
     <string name="permlab_vibrate" msgid="8596800035791962017">"controlează vibrarea"</string>
     <string name="permdesc_vibrate" msgid="8733343234582083721">"Permite aplicației să controleze mecanismul de vibrare."</string>
     <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Permite aplicației să acceseze modul de vibrații."</string>
-    <string name="permlab_callPhone" msgid="1798582257194643320">"apelare directă numere de telefon"</string>
-    <string name="permdesc_callPhone" msgid="5439809516131609109">"Permite aplicației să apeleze numere de telefon fără intervenția dvs. Acest lucru poate determina apariția unor taxe sau a unor apeluri neașteptate. Cu această permisiune aplicația nu poate apela numerele de urgență. Aplicațiile rău intenționate pot acumula costuri prin efectuarea unor apeluri fără confirmare."</string>
+    <string name="permlab_callPhone" msgid="1798582257194643320">"să sune direct la numere de telefon"</string>
+    <string name="permdesc_callPhone" msgid="5439809516131609109">"Permite aplicației să apeleze numere de telefon fără intervenția ta. Acest lucru poate determina apariția unor taxe sau a unor apeluri neașteptate. Cu această permisiune aplicația nu poate apela numerele de urgență. Aplicațiile rău intenționate pot acumula costuri prin efectuarea unor apeluri fără confirmare."</string>
     <string name="permlab_accessImsCallService" msgid="442192920714863782">"accesează serviciul de apelare IMS"</string>
-    <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite aplicației să folosească serviciul IMS pentru apeluri, fără intervenția dvs."</string>
+    <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite aplicației să folosească serviciul IMS pentru apeluri, fără intervenția ta."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"citește starea și identitatea telefonului"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite aplicației să acceseze funcțiile de telefon ale dispozitivului. Cu această permisiune aplicația stabilește numărul de telefon și ID-urile de dispozitiv, dacă un apel este activ, precum și numărul de la distanță conectat printr-un apel."</string>
     <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"să citească informații de bază, precum activitatea și starea telefonului"</string>
     <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite ca aplicația să acceseze funcțiile de telefonie de bază ale dispozitivului."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"să direcționeze apelurile prin intermediul sistemului"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite aplicației să direcționeze apelurile prin intermediul sistemului pentru a îmbunătăți calitatea apelurilor."</string>
-    <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Vedeți și controlați apelurile prin intermediul sistemului."</string>
+    <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Vezi și controlează apelurile prin intermediul sistemului."</string>
     <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Permite aplicației să vadă și să controleze apelurile în desfășurare pe dispozitiv. Aceasta include informații ca numerele pentru apeluri și starea apelurilor."</string>
     <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"scutită de restricțiile pentru înregistrarea conținutului audio"</string>
     <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Scutiți aplicația de restricțiile pentru înregistrarea conținutului audio."</string>
@@ -501,10 +501,10 @@
     <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Permite aplicației să schimbe fusul orar al dispozitivului Android TV."</string>
     <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Permite aplicației să schimbe fusul orar al telefonului."</string>
     <string name="permlab_getAccounts" msgid="5304317160463582791">"găsește conturi pe dispozitiv"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Permite aplicației să obțină lista de conturi cunoscute de tabletă. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Permite aplicației să obțină lista conturilor cunoscute de dispozitivul Android TV. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Permite aplicației să obțină lista de conturi cunoscute de telefon. Aceasta poate include conturile create de aplicațiile pe care le-ați instalat."</string>
-    <string name="permlab_accessNetworkState" msgid="2349126720783633918">"vizualizează conexiunile la rețea"</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Permite aplicației să obțină lista de conturi cunoscute de tabletă. Aceasta poate include conturile create de aplicațiile pe care le-ai instalat."</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Permite aplicației să obțină lista conturilor cunoscute de dispozitivul Android TV. Aceasta poate include conturile create de aplicațiile pe care le-ai instalat."</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Permite aplicației să obțină lista de conturi cunoscute de telefon. Aceasta poate include conturile create de aplicațiile pe care le-ai instalat."</string>
+    <string name="permlab_accessNetworkState" msgid="2349126720783633918">"să vadă conexiunile la rețea"</string>
     <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Permite aplicației să vadă informațiile despre conexiunile la rețea, cum ar fi rețelele existente și cele care sunt conectate."</string>
     <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"să aibă acces deplin la rețea"</string>
     <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Permite aplicației să creeze socluri de rețea și să utilizeze protocoale de rețea personalizate. Browserul și alte aplicații oferă mijloacele de trimitere a datelor pe internet, astfel încât această permisiune nu este necesară pentru trimiterea datelor pe internet."</string>
@@ -512,28 +512,28 @@
     <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Permite aplicației să modifice starea de conectivitate la rețea."</string>
     <string name="permlab_changeTetherState" msgid="9079611809931863861">"modificare conectivitate tethering"</string>
     <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Permite aplicației să modifice starea de conectivitate prin tethering la rețea."</string>
-    <string name="permlab_accessWifiState" msgid="5552488500317911052">"vizualizează conexiunile Wi-Fi"</string>
+    <string name="permlab_accessWifiState" msgid="5552488500317911052">"să vadă conexiunile Wi-Fi"</string>
     <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Permite aplicației să vadă informațiile despre rețelele Wi-Fi, de ex. dacă o rețea Wi-Fi este activată, precum și numele dispozitivelor conectate la rețeaua Wi-Fi."</string>
     <string name="permlab_changeWifiState" msgid="7947824109713181554">"se conectează și se deconectează de la Wi-Fi"</string>
     <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Permite aplicației să se conecteze și să se deconecteze de la punctele de acces Wi-Fi, precum și să efectueze modificări în configurația dispozitivului pentru rețelele Wi-Fi."</string>
     <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"permitere recepționare difuzare multiplă Wi-Fi"</string>
-    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, utilizând adrese cu difuzare multiplă, nu doar tableta dvs. Această funcție utilizează mai multă energie decât modul fără difuzare multiplă."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, folosind adrese cu difuzare multiplă, nu doar tableta ta. Această funcție folosește mai multă energie decât modul fără difuzare multiplă."</string>
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, utilizând adrese cu difuzare multiplă, nu doar dispozitivul Android TV. Această funcție utilizează mai multă energie decât modul fără difuzare multiplă."</string>
-    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, utilizând adrese cu difuzare multiplă, nu doar telefonul dvs. Această funcție utilizează mai multă energie decât modul fără difuzare multiplă."</string>
+    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, folosind adrese cu difuzare multiplă, nu doar telefonul tău. Această funcție folosește mai multă energie decât modul fără difuzare multiplă."</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"accesează setările Bluetooth"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Permite aplicației să configureze tableta Bluetooth locală, să descopere și să se împerecheze cu dispozitive la distanță."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Permite aplicației să configureze conexiunea Bluetooth pe dispozitivul Android TV, să descopere și să se împerecheze cu dispozitive la distanță."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Permite aplicației să configureze telefonul Bluetooth local, să descopere și să se împerecheze cu dispozitive la distanță."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Permite aplicației să configureze tableta Bluetooth locală, să descopere și să se asocieze cu dispozitive la distanță."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Permite aplicației să configureze conexiunea Bluetooth pe dispozitivul Android TV, să descopere și să se asocieze cu dispozitive la distanță."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Permite aplicației să configureze telefonul Bluetooth local, să descopere și să se asocieze cu dispozitive la distanță."</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"se conectează și se deconectează de la WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Permite aplicației să stabilească dacă o rețea WiMAX este activată și să vadă informațiile cu privire la toate rețelele WiMAX conectate."</string>
-    <string name="permlab_changeWimaxState" msgid="6223305780806267462">"schimbați starea WiMAX"</string>
+    <string name="permlab_changeWimaxState" msgid="6223305780806267462">"schimbă starea WiMAX"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Permite aplicației să conecteze și să deconecteze tableta la și de la rețelele WiMAX."</string>
     <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Permite aplicației să conecteze și să deconecteze dispozitivul Android TV de la rețelele WiMAX."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Permite aplicației să conecteze și să deconecteze telefonul la și de la rețelele WiMAX."</string>
     <string name="permlab_bluetooth" msgid="586333280736937209">"conectează dispozitive Bluetooth"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite aplicației să vadă configurația tabletei Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite aplicației să vadă configurația conexiunii prin Bluetooth a dispozitivului Android TV, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite aplicației să vadă configurația telefonului Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite aplicației să vadă configurația tabletei Bluetooth, să facă și să accepte conexiuni cu dispozitive asociate."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite aplicației să vadă configurația conexiunii prin Bluetooth a dispozitivului Android TV, să efectueze și să accepte conexiuni cu dispozitive asociate."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite aplicației să vadă configurația telefonului Bluetooth, să stabilească și să accepte conexiuni cu dispozitive asociate."</string>
     <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"să descopere și să asocieze dispozitive Bluetooth din apropiere"</string>
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Permite aplicației să descopere și să asocieze dispozitive Bluetooth din apropiere"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"să se conecteze la dispozitive Bluetooth asociate"</string>
@@ -550,57 +550,57 @@
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite aplicației să comunice cu etichetele, cardurile și cititoarele NFC (Near Field Communication)."</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">"solicitați complexitatea blocării ecranului"</string>
-    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Permite aplicației să învețe nivelul de complexitate al blocării ecranului (ridicat, mediu, scăzut sau fără), fapt ce indică intervalul posibil de lungime a parolei și tipul de blocare a ecranului. Aplicația le poate sugera utilizatorilor să își actualizeze blocarea ecranului la un anumit nivel, dar utilizatorii pot ignora sugestia și pot naviga în continuare. Rețineți că blocarea ecranului nu este stocată ca text simplu, astfel încât aplicația să nu cunoască parola exactă."</string>
+    <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"să solicite complexitatea blocării ecranului"</string>
+    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Permite aplicației să învețe nivelul de complexitate al blocării ecranului (ridicat, mediu, scăzut sau fără), fapt ce indică intervalul posibil de lungime a parolei și tipul de blocare a ecranului. Aplicația le poate sugera utilizatorilor să își actualizeze blocarea ecranului la un anumit nivel, dar utilizatorii pot ignora sugestia și pot naviga în continuare. Reține că blocarea ecranului nu e stocată ca text simplu, astfel încât aplicația să nu cunoască parola exactă."</string>
     <string name="permlab_postNotification" msgid="4875401198597803658">"să afișeze notificări"</string>
     <string name="permdesc_postNotification" msgid="5974977162462877075">"Permite aplicației să afișeze notificări"</string>
     <string name="permlab_turnScreenOn" msgid="219344053664171492">"să activeze ecranul"</string>
     <string name="permdesc_turnScreenOn" msgid="4394606875897601559">"Permite aplicației să activeze ecranul."</string>
-    <string name="permlab_useBiometric" msgid="6314741124749633786">"utilizați hardware biometric"</string>
+    <string name="permlab_useBiometric" msgid="6314741124749633786">"să folosească hardware biometric"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Permite aplicației să folosească hardware biometric pentru autentificare"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"gestionează hardware-ul pentru amprentă"</string>
     <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite aplicației să invoce metode pentru a adăuga și pentru a șterge șabloane de amprentă pentru utilizare."</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"folosește hardware-ul pentru amprentă"</string>
     <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite aplicației să folosească hardware pentru amprentă pentru autentificare"</string>
-    <string name="permlab_audioWrite" msgid="8501705294265669405">"modificați colecția de muzică"</string>
-    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite aplicației să vă modifice colecția de muzică."</string>
-    <string name="permlab_videoWrite" msgid="5940738769586451318">"modificați colecția de videoclipuri"</string>
-    <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite aplicației să vă modifice colecția de videoclipuri."</string>
-    <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificați colecția de fotografii"</string>
-    <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Permite aplicației să vă modifice colecția de fotografii."</string>
-    <string name="permlab_mediaLocation" msgid="7368098373378598066">"citiți locațiile din colecția media"</string>
-    <string name="permdesc_mediaLocation" msgid="597912899423578138">"Permite aplicației să citească locațiile din colecția dvs. media."</string>
-    <string name="biometric_app_setting_name" msgid="3339209978734534457">"Folosiți sistemele biometrice"</string>
-    <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Folosiți sistemele biometrice sau blocarea ecranului"</string>
-    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmați-vă identitatea"</string>
-    <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Folosiți sistemele biometrice pentru a continua"</string>
-    <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Folosiți sistemele biometrice sau blocarea ecranului pentru a continua"</string>
+    <string name="permlab_audioWrite" msgid="8501705294265669405">"să modifice colecția de muzică"</string>
+    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite aplicației să modifice colecția de muzică."</string>
+    <string name="permlab_videoWrite" msgid="5940738769586451318">"să modifice colecția de videoclipuri"</string>
+    <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite aplicației să-ți modifice colecția de videoclipuri."</string>
+    <string name="permlab_imagesWrite" msgid="1774555086984985578">"să modifice colecția de fotografii"</string>
+    <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Permite aplicației să-ți modifice colecția de fotografii."</string>
+    <string name="permlab_mediaLocation" msgid="7368098373378598066">"să citească locațiile din colecția media"</string>
+    <string name="permdesc_mediaLocation" msgid="597912899423578138">"Permite aplicației să citească locațiile din colecția ta media."</string>
+    <string name="biometric_app_setting_name" msgid="3339209978734534457">"Folosește sistemele biometrice"</string>
+    <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Folosește sistemele biometrice sau blocarea ecranului"</string>
+    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmă-ți identitatea"</string>
+    <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Folosește sistemele biometrice pentru a continua"</string>
+    <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Folosește sistemele biometrice sau blocarea ecranului pentru a continua"</string>
     <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_error_canceled" msgid="8266582404844179778">"Autentificarea a fost anulată"</string>
-    <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nu este setat niciun cod PIN, model sau parolă"</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>
-    <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosiți blocarea ecranului"</string>
-    <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduceți blocarea ecranului ca să continuați"</string>
+    <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosește blocarea ecranului"</string>
+    <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introdu blocarea ecranului pentru a continua"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Apasă ferm pe senzor"</string>
     <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Amprenta nu a fost recunoscută. Încearcă din nou."</string>
-    <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Curățați senzorul de amprentă și încercați din nou"</string>
-    <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Curățați senzorul și încercați din nou"</string>
+    <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Curăță senzorul de amprentă și încearcă din nou"</string>
+    <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Curăță senzorul și încearcă din nou"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Apasă ferm pe senzor"</string>
-    <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Ați mișcat degetul prea lent. Încercați din nou."</string>
+    <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Ai mișcat degetul prea lent. Încearcă din nou."</string>
     <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Încearcă altă amprentă"</string>
     <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Prea luminos"</string>
     <string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"S-a detectat apăsarea butonului de alimentare"</string>
-    <string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Încercați să ajustați"</string>
-    <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Schimbați ușor poziția degetului de fiecare dată"</string>
+    <string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Încearcă să ajustezi"</string>
+    <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Schimbă ușor poziția degetului de fiecare dată"</string>
   <string-array name="fingerprint_acquired_vendor">
   </string-array>
     <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Amprenta nu a fost recunoscută"</string>
     <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Amprenta nu a fost recunoscută"</string>
     <string name="fingerprint_authenticated" msgid="2024862866860283100">"Amprentă autentificată"</string>
     <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Chip autentificat"</string>
-    <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Chip autentificat, apăsați Confirmați"</string>
+    <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Chip autentificat, apasă pe Confirmă"</string>
     <string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Hardware-ul pentru amprentă nu este disponibil."</string>
     <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Nu se poate configura amprenta"</string>
     <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Configurarea amprentei a expirat. Încearcă din nou."</string>
@@ -612,28 +612,28 @@
     <string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Nu au fost înregistrate amprente."</string>
     <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Dispozitivul nu are senzor de amprentă."</string>
     <string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Senzorul este dezactivat temporar."</string>
-    <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Nu se poate folosi senzorul de amprentă. Vizitați un furnizor de servicii de reparații."</string>
+    <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Nu se poate folosi senzorul de amprentă. Vizitează un furnizor de servicii de reparații."</string>
     <string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"A fost apăsat butonul de pornire"</string>
     <string name="fingerprint_name_template" msgid="8941662088160289778">"Degetul <xliff:g id="FINGERID">%d</xliff:g>"</string>
-    <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Folosiți amprenta"</string>
-    <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Folosiți amprenta sau blocarea ecranului"</string>
-    <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Folosiți amprenta pentru a continua"</string>
-    <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Folosiți amprenta sau blocarea ecranului pentru a continua"</string>
+    <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Folosește amprenta"</string>
+    <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Folosește amprenta sau blocarea ecranului"</string>
+    <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Folosește amprenta pentru a continua"</string>
+    <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Folosește amprenta sau blocarea ecranului pentru a continua"</string>
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_error_vendor_unknown" msgid="4170002184907291065">"A apărut o eroare. Încearcă din nou."</string>
     <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Pictograma amprentă"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Deblocare facială"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problemă cu Deblocarea facială"</string>
-    <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Atingeți pentru a șterge modelul facial, apoi adăugați din nou fața"</string>
+    <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Atinge pentru a șterge modelul facial, apoi adaugă din nou chipul"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurează Deblocarea facială"</string>
-    <string name="face_setup_notification_content" msgid="5463999831057751676">"Deblocați-vă telefonul uitându-vă la acesta"</string>
-    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Pentru a folosi Deblocarea facială, activați "<b>"Accesul la cameră"</b>" în Setări și confidențialitate"</string>
+    <string name="face_setup_notification_content" msgid="5463999831057751676">"Deblochează-ți telefonul uitându-te la el"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Pentru a folosi Deblocarea facială, activează "<b>"Accesul la cameră"</b>" în Setări și confidențialitate"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configurează mai multe moduri de deblocare"</string>
-    <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Atingeți ca să adăugați o amprentă"</string>
+    <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Atinge ca să adaugi o amprentă"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Deblocare cu amprenta"</string>
     <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Nu se poate folosi senzorul de amprentă"</string>
-    <string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Vizitați un furnizor de servicii de reparații."</string>
+    <string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Vizitează un furnizor de servicii de reparații."</string>
     <string name="face_acquired_insufficient" msgid="6889245852748492218">"Nu se poate crea modelul facial. Reîncearcă."</string>
     <string name="face_acquired_too_bright" msgid="8070756048978079164">"Prea luminos. Încearcă o lumină mai slabă."</string>
     <string name="face_acquired_too_dark" msgid="8539853432479385326">"Lumină insuficientă"</string>
@@ -643,17 +643,17 @@
     <string name="face_acquired_too_low" msgid="4075391872960840081">"Mută telefonul mai jos"</string>
     <string name="face_acquired_too_right" msgid="6245286514593540859">"Mută telefonul spre stânga"</string>
     <string name="face_acquired_too_left" msgid="9201762240918405486">"Mută telefonul spre dreapta"</string>
-    <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Priviți mai direct spre dispozitiv."</string>
-    <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nu vi se vede fața. Țineți telefonul la nivelul ochilor."</string>
-    <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Prea multă mișcare. Țineți telefonul nemișcat."</string>
-    <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrați-vă chipul."</string>
+    <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Privește mai direct spre dispozitiv."</string>
+    <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nu ți se vede fața. Ține telefonul la nivelul ochilor."</string>
+    <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Prea multă mișcare. Ține telefonul nemișcat."</string>
+    <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrează-ți chipul."</string>
     <string name="face_acquired_too_different" msgid="2520389515612972889">"Chipul nu a fost recunoscut. Reîncearcă."</string>
-    <string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbați ușor poziția capului"</string>
+    <string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbă ușor poziția capului"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Priviți direct spre telefon"</string>
-    <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Priviți direct spre telefon"</string>
-    <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"Priviți direct spre telefon"</string>
-    <string name="face_acquired_obscured" msgid="4917643294953326639">"Eliminați orice vă ascunde chipul."</string>
-    <string name="face_acquired_sensor_dirty" msgid="8968391891086721678">"Curățați partea de sus a ecranului, inclusiv bara neagră"</string>
+    <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Privește mai direct spre telefon"</string>
+    <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"Privește mai direct spre telefon"</string>
+    <string name="face_acquired_obscured" msgid="4917643294953326639">"Îndepărtează orice îți ascunde chipul."</string>
+    <string name="face_acquired_sensor_dirty" msgid="8968391891086721678">"Curăță partea de sus a ecranului, inclusiv bara neagră"</string>
     <!-- no translation found for face_acquired_dark_glasses_detected (5643703296620631986) -->
     <skip />
     <!-- no translation found for face_acquired_mouth_covering_detected (8219428572168642593) -->
@@ -670,21 +670,21 @@
     <string name="face_error_user_canceled" msgid="5766472033202928373">"Deblocarea facială a fost anulată de utilizator"</string>
     <string name="face_error_lockout" msgid="7864408714994529437">"Prea multe încercări. Reîncearcă mai târziu."</string>
     <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Prea multe încercări. Deblocarea facială este dezactivată."</string>
-    <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Prea multe încercări. Folosiți blocarea ecranului."</string>
+    <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Prea multe încercări. Folosește blocarea ecranului."</string>
     <string name="face_error_unable_to_process" msgid="5723292697366130070">"Nu se poate confirma fața. Încearcă din nou."</string>
-    <string name="face_error_not_enrolled" msgid="1134739108536328412">"Nu ați configurat Deblocarea facială"</string>
+    <string name="face_error_not_enrolled" msgid="1134739108536328412">"Nu ai configurat Deblocarea facială"</string>
     <string name="face_error_hw_not_present" msgid="7940978724978763011">"Deblocarea facială nu este acceptată pe acest dispozitiv"</string>
     <string name="face_error_security_update_required" msgid="5076017208528750161">"Senzorul este dezactivat temporar."</string>
     <string name="face_name_template" msgid="3877037340223318119">"Chip <xliff:g id="FACEID">%d</xliff:g>"</string>
-    <string name="face_app_setting_name" msgid="5854024256907828015">"Folosiți Deblocarea facială"</string>
-    <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Folosiți deblocarea facială sau ecranul de blocare"</string>
-    <string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Folosiți-vă chipul ca să continuați"</string>
-    <string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Folosiți-vă chipul sau blocarea ecranului pentru a continua"</string>
+    <string name="face_app_setting_name" msgid="5854024256907828015">"Folosește Deblocarea facială"</string>
+    <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Folosește deblocarea facială sau ecranul de blocare"</string>
+    <string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Folosește-ți chipul pentru a continua"</string>
+    <string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Folosește-ți chipul sau blocarea ecranului pentru a continua"</string>
   <string-array name="face_error_vendor">
   </string-array>
     <string name="face_error_vendor_unknown" msgid="7387005932083302070">"A apărut o eroare. Încearcă din nou."</string>
     <string name="face_icon_content_description" msgid="465030547475916280">"Pictograma chip"</string>
-    <string name="permlab_readSyncSettings" msgid="6250532864893156277">"citire setări sincronizare"</string>
+    <string name="permlab_readSyncSettings" msgid="6250532864893156277">"să citească setări sincronizare"</string>
     <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Permite aplicației să citească setările de sincronizare ale unui cont. De exemplu, cu această permisiune aplicația poate determina dacă aplicația Persoane este sincronizată cu un anumit cont."</string>
     <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"activează/dezactivează sincronizarea"</string>
     <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Permite unei aplicații să modifice setările de sincronizare ale unui cont. De exemplu, cu această permisiune aplicația poate activa sincronizarea aplicației Persoane cu un anumit cont."</string>
@@ -711,14 +711,14 @@
     <string name="permlab_bind_incall_service" msgid="5990625112603493016">"interacțiune cu ecranul în timpul unui apel"</string>
     <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Permite aplicației să controleze când și cum vede utilizatorul ecranul în timpul unui apel."</string>
     <string name="permlab_bind_connection_service" msgid="5409268245525024736">"să interacționeze cu servicii de telefonie"</string>
-    <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Permite aplicației să interacționeze cu servicii de telefonie pentru a da / a primi apeluri."</string>
+    <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Permite aplicației să interacționeze cu servicii de telefonie pentru a face / a primi apeluri."</string>
     <string name="permlab_control_incall_experience" msgid="6436863486094352987">"oferă o experiență de utilizare în timpul unui apel"</string>
     <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Permite aplicației să ofere o experiență de utilizare în timpul unui apel."</string>
     <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"citește utilizarea statistică a rețelei"</string>
     <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Permite aplicației să citească utilizarea statistică a rețelei pentru anumite rețele și aplicații."</string>
     <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"gestionează politica de rețea"</string>
     <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Permite aplicației să gestioneze politicile de rețea și să definească regulile specifice aplicațiilor."</string>
-    <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"modificați modul de calcul al utilizării rețelei"</string>
+    <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"să modifice modul de calcul al utilizării rețelei"</string>
     <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Permite aplicației să modifice modul în care este calculată utilizarea rețelei pentru aplicații. Nu se utilizează de aplicațiile obișnuite."</string>
     <string name="permlab_accessNotifications" msgid="7130360248191984741">"accesare notificări"</string>
     <string name="permdesc_accessNotifications" msgid="761730149268789668">"Permite aplicației să recupereze, să examineze și să șteargă notificări, inclusiv pe cele postate de alte aplicații."</string>
@@ -732,7 +732,7 @@
     <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"Permite proprietarului să apeleze aplicația de configurare furnizată de operator. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"ascultă observații despre starea rețelei"</string>
     <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Permite unei aplicații să asculte observații despre starea rețelei. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
-    <string name="permlab_setInputCalibration" msgid="932069700285223434">"schimbați calibrarea dispozitivului de intrare"</string>
+    <string name="permlab_setInputCalibration" msgid="932069700285223434">"schimbă calibrarea dispozitivului de intrare"</string>
     <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Permite aplicației să modifice parametrii de calibrare a ecranului tactil. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"accesează certificatele DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"Permite unei aplicații să furnizeze și să utilizeze certificate DRM. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
@@ -746,50 +746,50 @@
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite aplicației să se conecteze la serviciile operatorului. Nu ar trebui să fie niciodată necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"accesează Nu deranja"</string>
     <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite aplicației să citească și să scrie configurația Nu deranja."</string>
-    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"porniți folosirea permisiunii de vizualizare"</string>
+    <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"să înceapă folosirea permisiunii de vizualizare"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite proprietarului să pornească folosirea permisiunii pentru o aplicație. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"să înceapă să examineze deciziile privind permisiunile"</string>
     <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite proprietarului să deschidă ecranul pentru a examina deciziile privind permisiunile. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
-    <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"începeți să vedeți funcțiile aplicației"</string>
+    <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"să vadă funcțiile aplicației"</string>
     <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite proprietarului să înceapă să vadă informațiile despre funcții pentru o aplicație."</string>
     <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"să acceseze date de la senzori la o rată de eșantionare mare"</string>
     <string name="permdesc_highSamplingRateSensors" msgid="8430061978931155995">"Permite aplicației să colecteze date de la senzori la o rată de eșantionare de peste 200 Hz"</string>
     <string name="policylab_limitPassword" msgid="4851829918814422199">"Să seteze reguli pentru parolă"</string>
-    <string name="policydesc_limitPassword" msgid="4105491021115793793">"Stabiliți lungimea și tipul de caractere permise pentru parolele și codurile PIN de blocare a ecranului."</string>
+    <string name="policydesc_limitPassword" msgid="4105491021115793793">"Stabilește lungimea și tipul de caractere permise pentru parolele și codurile PIN de blocare a ecranului."</string>
     <string name="policylab_watchLogin" msgid="7599669460083719504">"Să monitorizeze încercările de deblocare a ecranului"</string>
-    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați tableta sau ștergeți datele acesteia dacă sunt introduse prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați dispozitivul Android TV sau ștergeți toate datele de pe acesta dacă se introduc prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați sistemul de infotainment sau ștergeți toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați telefonul sau ștergeți toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați tableta sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați dispozitivul Android TV sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați sistemul de infotainment sau ștergeți toate datele acestui profil dacă sunt introduse prea multe parole incorecte."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitorizați numărul de parole incorecte introduse la deblocarea ecranului și blocați telefonul sau ștergeți toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează tableta sau șterge datele acesteia dacă sunt introduse prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează dispozitivul Android TV sau șterge toate datele de pe acesta dacă se introduc prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează sistemul de infotainment sau șterge toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează telefonul sau șterge toate datele acestuia dacă sunt introduse prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează tableta sau șterge toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează dispozitivul Android TV sau șterge toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează sistemul de infotainment sau șterge toate datele acestui profil dacă sunt introduse prea multe parole incorecte."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitorizează numărul de parole incorecte introduse la deblocarea ecranului și blochează telefonul sau șterge toate datele acestui utilizator dacă se introduc prea multe parole incorecte."</string>
     <string name="policylab_resetPassword" msgid="214556238645096520">"Să schimbe blocarea ecranului"</string>
-    <string name="policydesc_resetPassword" msgid="4626419138439341851">"Modificați blocarea ecranului."</string>
+    <string name="policydesc_resetPassword" msgid="4626419138439341851">"Modifică blocarea ecranului."</string>
     <string name="policylab_forceLock" msgid="7360335502968476434">"Să blocheze ecranul"</string>
-    <string name="policydesc_forceLock" msgid="1008844760853899693">"Stabiliți modul și timpul în care se blochează ecranul."</string>
+    <string name="policydesc_forceLock" msgid="1008844760853899693">"Stabilește cum și când se blochează ecranul."</string>
     <string name="policylab_wipeData" msgid="1359485247727537311">"Să șteargă toate datele"</string>
     <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Șterge datele de pe tabletă fără avertisment, efectuând resetarea configurării din fabrică."</string>
     <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Șterge datele de pe dispozitivul Android TV fără avertisment, efectuând o revenire la setările din fabrică."</string>
     <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Șterge datele din sistemul de infotainment fără avertisment, prin revenirea la setările din fabrică."</string>
-    <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Șterge datele din telefon fără avertisment, efectuând resetarea configurării din fabrică."</string>
+    <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Șterge datele din telefon fără avertisment, revenind la setările din fabrică."</string>
     <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Șterge datele de profil"</string>
     <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Șterge datele utilizatorului"</string>
     <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Șterge datele acestui utilizator de pe această tabletă fără avertisment."</string>
     <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Șterge datele acestui utilizator de pe acest dispozitiv Android TV fără avertisment"</string>
     <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Șterge datele profilului din acest sistem de infotainment fără avertisment."</string>
     <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Șterge datele acestui utilizator de pe acest telefon fără avertisment."</string>
-    <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Setați serverul proxy global pentru dispozitiv"</string>
-    <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Setați serverul proxy global pentru dispozitiv, care să fie utilizat cât timp politica este activă. Numai proprietarul dispozitivului poate seta serverul proxy global."</string>
-    <string name="policylab_expirePassword" msgid="6015404400532459169">"Setați expirarea parolei pentru blocarea ecranului"</string>
-    <string name="policydesc_expirePassword" msgid="9136524319325960675">"Modificați frecvența cu care trebuie să se schimbe parola, codul PIN sau modelul pentru blocarea ecranului."</string>
+    <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Setează serverul proxy global pentru dispozitiv"</string>
+    <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Setează serverul proxy global pentru dispozitiv, care să fie utilizat cât timp politica este activă. Numai proprietarul dispozitivului poate seta serverul proxy global."</string>
+    <string name="policylab_expirePassword" msgid="6015404400532459169">"Setează expirarea parolei pentru blocarea ecranului"</string>
+    <string name="policydesc_expirePassword" msgid="9136524319325960675">"Modifică frecvența cu care trebuie să se schimbe parola, codul PIN sau modelul pentru blocarea ecranului."</string>
     <string name="policylab_encryptedStorage" msgid="9012936958126670110">"Să seteze criptarea stocării"</string>
     <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Necesită ca datele aplicației stocate să fie criptate."</string>
     <string name="policylab_disableCamera" msgid="5749486347810162018">"Să dezactiveze camerele foto"</string>
-    <string name="policydesc_disableCamera" msgid="3204405908799676104">"Împiedicați utilizarea camerelor foto de pe dispozitiv."</string>
+    <string name="policydesc_disableCamera" msgid="3204405908799676104">"Împiedică folosirea camerelor foto de pe dispozitiv."</string>
     <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"Să oprească funcții de blocare ecran"</string>
-    <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Împiedicați folosirea unor funcții de blocare a ecranului."</string>
+    <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Împiedică folosirea unor funcții de blocare a ecranului."</string>
   <string-array name="phoneTypes">
     <item msgid="8996339953292723951">"Domiciliu"</item>
     <item msgid="7740243458912727194">"Mobil"</item>
@@ -908,23 +908,23 @@
     <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Introdu codul PUK și noul cod PIN"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"Codul PUK"</string>
     <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"Noul cod PIN"</string>
-    <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Atingeți ca să introduceți parola"</font></string>
+    <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Atinge ca să introduci parola"</font></string>
     <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Introdu parola pentru a debloca"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introdu codul PIN pentru a debloca"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Cod PIN incorect."</string>
-    <string name="keyguard_label_text" msgid="3841953694564168384">"Pentru a debloca, apăsați Meniu, apoi 0."</string>
+    <string name="keyguard_label_text" msgid="3841953694564168384">"Pentru a debloca, apasă Meniu, apoi 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Număr de urgență"</string>
     <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Fără semnal"</string>
     <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Ecranul este blocat."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Apasă Meniu pentru a debloca sau pentru a efectua apeluri de urgență."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Apasă Meniu pentru deblocare."</string>
-    <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenați modelul pentru a debloca"</string>
+    <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenează modelul pentru a debloca"</string>
     <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgență"</string>
-    <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Reveniți la apel"</string>
+    <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Revino la apel"</string>
     <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Corect!"</string>
     <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Încearcă din nou"</string>
     <string name="lockscreen_password_wrong" msgid="8605355913868947490">"Încearcă din nou"</string>
-    <string name="lockscreen_storage_locked" msgid="634993789186443380">"Deblocați pentru toate funcțiile și datele"</string>
+    <string name="lockscreen_storage_locked" msgid="634993789186443380">"Deblochează pentru toate funcțiile și datele"</string>
     <string name="faceunlock_multiple_failures" msgid="681991538434031708">"S-a depășit numărul maxim de încercări pentru Deblocare facială"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"Fără SIM"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"Nu există card SIM în computerul tablet PC."</string>
@@ -933,44 +933,44 @@
     <string name="lockscreen_missing_sim_instructions" msgid="8473601862688263903">"Introdu un card SIM."</string>
     <string name="lockscreen_missing_sim_instructions_long" msgid="3664999892038416334">"Cardul SIM lipsește sau nu poate fi citit. Introdu un card SIM."</string>
     <string name="lockscreen_permanent_disabled_sim_message_short" msgid="3812893366715730539">"Card SIM inutilizabil."</string>
-    <string name="lockscreen_permanent_disabled_sim_instructions" msgid="4358929052509450807">"Cardul dvs. SIM este dezactivat definitiv.\n Contactați furnizorul de servicii wireless pentru a obține un alt card SIM."</string>
+    <string name="lockscreen_permanent_disabled_sim_instructions" msgid="4358929052509450807">"Cardul SIM este dezactivat definitiv.\n Contactează furnizorul de servicii wireless pentru a obține un alt card SIM."</string>
     <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Melodia anterioară"</string>
     <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Melodia următoare"</string>
     <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pauză"</string>
-    <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Redați"</string>
+    <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Redă"</string>
     <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Oprește"</string>
-    <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Derulați"</string>
-    <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Derulați rapid înainte"</string>
+    <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Derulează"</string>
+    <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Derulează rapid înainte"</string>
     <string name="emergency_calls_only" msgid="3057351206678279851">"Numai apeluri de urgență"</string>
     <string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Rețea blocată"</string>
     <string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"Cardul SIM este blocat cu codul PUK."</string>
-    <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Consultați Ghidul de utilizare sau contactați Serviciul de relații cu clienții."</string>
+    <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Consultă Ghidul de utilizare sau contactează asistența pentru clienți."</string>
     <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"Cardul SIM este blocat."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"Se deblochează cardul SIM..."</string>
-    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul datelor de conectare la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g>   secunde."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați dispozitivul Android TV prin conectarea la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul datelor de conectare la Google.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g>   secunde."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va fi resetată la setările prestabilite din fabrică, iar toate datele de utilizator vor fi pierdute."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va fi resetat la setările prestabilite din fabrică, iar toate datele de utilizator vor fi pierdute."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va fi acum resetată la setările prestabilite din fabrică."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acesta va fi acum resetat la setările prestabilite din fabrică."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"Ai introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Ai introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi tableta cu ajutorul datelor de conectare la Google.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi dispozitivul Android TV prin conectarea la Google.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi telefonul cu ajutorul datelor de conectare la Google.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g>   secunde."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va reveni acum la setările din fabrică."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acesta va reveni acum la setările din fabrică."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Încearcă din nou peste <xliff:g id="NUMBER">%d</xliff:g>   secunde."</string>
-    <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Ați uitat modelul?"</string>
+    <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Ai uitat modelul?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Deblocare cont"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Prea multe încercări de desenare a modelului"</string>
-    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Pentru a debloca, conectați-vă folosind Contul Google."</string>
+    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Pentru a debloca, conectează-te folosind Contul Google."</string>
     <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Nume de utilizator (e-mail)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Parolă"</string>
     <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Conectează-te"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Nume de utilizator sau parolă nevalide."</string>
-    <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Ați uitat numele de utilizator sau parola?\nAccesați "<b>"google.com/accounts/recovery"</b>"."</string>
+    <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Ai uitat numele de utilizator sau parola?\nAccesează "<b>"google.com/accounts/recovery"</b>"."</string>
     <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Se verifică..."</string>
-    <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Deblocați"</string>
+    <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Deblochează"</string>
     <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sunet activat"</string>
     <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"Sunet dezactivat"</string>
     <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"Desenarea modelului a început"</string>
@@ -1015,15 +1015,15 @@
     <string name="factorytest_reboot" msgid="2050147445567257365">"Repornește"</string>
     <string name="js_dialog_title" msgid="7464775045615023241">"La pagina de la „<xliff:g id="TITLE">%s</xliff:g>” apare:"</string>
     <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
-    <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Confirmați părăsirea paginii"</string>
-    <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Părăsiți această pagină"</string>
-    <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Rămâneți în această pagină"</string>
-    <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nSigur doriți să părăsiți această pagină?"</string>
-    <string name="save_password_label" msgid="9161712335355510035">"Confirmați"</string>
-    <string name="double_tap_toast" msgid="7065519579174882778">"Sfat: măriți și micșorați prin dublă atingere."</string>
+    <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Confirmă părăsirea paginii"</string>
+    <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Părăsește această pagină"</string>
+    <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Rămâi în această pagină"</string>
+    <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nSigur părăsești această pagină?"</string>
+    <string name="save_password_label" msgid="9161712335355510035">"Confirmă"</string>
+    <string name="double_tap_toast" msgid="7065519579174882778">"Sfat: mărește și micșorează prin dublă atingere."</string>
     <string name="autofill_this_form" msgid="3187132440451621492">"Automat"</string>
     <string name="setup_autofill" msgid="5431369130866618567">"Conf.Compl.auto."</string>
-    <string name="autofill_window_title" msgid="4379134104008111961">"Completați automat cu <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
+    <string name="autofill_window_title" msgid="4379134104008111961">"Completează automat cu <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
     <string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
     <string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
     <string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
@@ -1052,11 +1052,11 @@
     <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Permite aplicației să adauge mesaje în Mesaje primite în mesageria vocală."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"modificare permisiuni pentru locația geografică a browserului"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Permite aplicației să modifice permisiunile privind locația geografică a browserului. Aplicațiile rău intenționate pot utiliza această permisiune pentru a permite trimiterea informațiilor privind locația către site-uri web arbitrare."</string>
-    <string name="save_password_message" msgid="2146409467245462965">"Doriți ca browserul să rețină această parolă?"</string>
+    <string name="save_password_message" msgid="2146409467245462965">"Vrei ca browserul să rețină această parolă?"</string>
     <string name="save_password_notnow" msgid="2878327088951240061">"Nu acum"</string>
-    <string name="save_password_remember" msgid="6490888932657708341">"Rețineți"</string>
+    <string name="save_password_remember" msgid="6490888932657708341">"Reține"</string>
     <string name="save_password_never" msgid="6776808375903410659">"Niciodată"</string>
-    <string name="open_permission_deny" msgid="5136793905306987251">"Nu aveți permisiunea de a deschide această pagină."</string>
+    <string name="open_permission_deny" msgid="5136793905306987251">"Nu ai permisiunea de a deschide această pagină."</string>
     <string name="text_copied" msgid="2531420577879738860">"Text copiat în clipboard."</string>
     <string name="pasted_from_app" msgid="5627698450808256545">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> a inserat date din <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
     <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> a inserat din clipboard"</string>
@@ -1081,9 +1081,9 @@
     <string name="searchview_description_clear" msgid="1989371719192982900">"Șterge interogarea"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"Trimite interogarea"</string>
     <string name="searchview_description_voice" msgid="42360159504884679">"Căutare vocală"</string>
-    <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Activați Explorați prin atingere?"</string>
-    <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorați prin atingere. Când această funcție este activată, puteți auzi sau vedea descrieri pentru ceea ce se află sub degetul dvs. sau puteți efectua gesturi pentru a interacționa cu tableta."</string>
-    <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorați prin atingere. Când această funcție este activată, puteți auzi sau vedea descrieri pentru ceea ce se află sub degetul dvs. sau puteți efectua gesturi pentru a interacționa cu telefonul."</string>
+    <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Activezi Explorează prin atingere?"</string>
+    <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vrea să activeze funcția Explorează prin atingere. Când e activată, poți auzi sau vedea descrieri pentru ceea ce se află sub degetul tău sau poți face gesturi pentru a interacționa cu tableta."</string>
+    <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> dorește să activeze funcția Explorează prin atingere. Când aceasta e activată, poți auzi sau vedea descrieri pentru ceea ce se află sub degetul tău sau poți face gesturi pentru a interacționa cu telefonul."</string>
     <string name="oneMonthDurationPast" msgid="4538030857114635777">"cu 1 lună în urmă"</string>
     <string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Cu mai mult de 1 lună în urmă"</string>
     <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Ultima zi}few{Ultimele # zile}other{Ultimele # de zile}}"</string>
@@ -1096,9 +1096,9 @@
     <string name="days" msgid="4570879797423034973">"   zile"</string>
     <string name="hour" msgid="7796325297097314653">"oră"</string>
     <string name="hours" msgid="8517014849629200683">"ore"</string>
-    <string name="minute" msgid="8369209540986467610">"min"</string>
+    <string name="minute" msgid="8369209540986467610">"min."</string>
     <string name="minutes" msgid="3456532942641808971">"min."</string>
-    <string name="second" msgid="9210875257112211713">"sec"</string>
+    <string name="second" msgid="9210875257112211713">"sec."</string>
     <string name="seconds" msgid="2175052687727971048">"sec."</string>
     <string name="week" msgid="907127093960923779">"săptămână"</string>
     <string name="weeks" msgid="3516247214269821391">"săptămâni"</string>
@@ -1123,7 +1123,7 @@
     <string name="duration_years_relative_future" msgid="8855853883925918380">"{count,plural, =1{# an}few{# ani}other{# de ani}}"</string>
     <string name="VideoView_error_title" msgid="5750686717225068016">"Problemă video"</string>
     <string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Acest fișier video nu este valid pentru a fi transmis în flux către acest dispozitiv."</string>
-    <string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Nu puteți reda acest videoclip"</string>
+    <string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Nu poți reda acest videoclip"</string>
     <string name="VideoView_error_button" msgid="5138809446603764272">"OK"</string>
     <string name="relative_time" msgid="8572030016028033243">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="noon" msgid="8365974533050605886">"prânz"</string>
@@ -1138,12 +1138,12 @@
     <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Eroare la copierea în clipboard"</string>
     <string name="paste" msgid="461843306215520225">"Inserează"</string>
     <string name="paste_as_plain_text" msgid="7664800665823182587">"Inserează ca text simplu"</string>
-    <string name="replace" msgid="7842675434546657444">"Înlocuiți..."</string>
+    <string name="replace" msgid="7842675434546657444">"Înlocuiește..."</string>
     <string name="delete" msgid="1514113991712129054">"Șterge"</string>
     <string name="copyUrl" msgid="6229645005987260230">"Copiază adresa URL"</string>
     <string name="selectTextMode" msgid="3225108910999318778">"Selectează text"</string>
     <string name="undo" msgid="3175318090002654673">"Anulează"</string>
-    <string name="redo" msgid="7231448494008532233">"Repetați"</string>
+    <string name="redo" msgid="7231448494008532233">"Repetă"</string>
     <string name="autofill" msgid="511224882647795296">"Completare automată"</string>
     <string name="textSelectionCABTitle" msgid="5151441579532476940">"Selectare text"</string>
     <string name="addToDictionary" msgid="8041821113480950096">"Adaugă în dicționar"</string>
@@ -1151,10 +1151,10 @@
     <string name="inputMethod" msgid="1784759500516314751">"Metodă de intrare"</string>
     <string name="editTextMenuTitle" msgid="857666911134482176">"Acțiuni pentru text"</string>
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Înapoi"</string>
-    <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Comutați metoda de introducere a textului"</string>
+    <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Schimbă metoda de introducere"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Spațiul de stocare aproape ocupat"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Este posibil ca unele funcții de sistem să nu funcționeze"</string>
-    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Spațiu de stocare insuficient pentru sistem. Asigurați-vă că aveți 250 MB de spațiu liber și reporniți."</string>
+    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Spațiu de stocare insuficient pentru sistem. Asigură-te că ai 250 MB de spațiu liber și repornește."</string>
     <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> rulează acum"</string>
     <string name="app_running_notification_text" msgid="5120815883400228566">"Atinge pentru mai multe informații sau pentru a opri aplicația."</string>
     <string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1172,8 +1172,8 @@
     <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{O stea din {max}}few{# stele din {max}}other{# de stele din {max}}}"</string>
     <string name="in_progress" msgid="2149208189184319441">"în curs"</string>
     <string name="whichApplication" msgid="5432266899591255759">"Finalizare acțiune utilizând"</string>
-    <string name="whichApplicationNamed" msgid="6969946041713975681">"Finalizați acțiunea utilizând %1$s"</string>
-    <string name="whichApplicationLabel" msgid="7852182961472531728">"Finalizați acțiunea"</string>
+    <string name="whichApplicationNamed" msgid="6969946041713975681">"Finalizează acțiunea folosind %1$s"</string>
+    <string name="whichApplicationLabel" msgid="7852182961472531728">"Finalizează acțiunea"</string>
     <string name="whichViewApplication" msgid="5733194231473132945">"Deschide cu"</string>
     <string name="whichViewApplicationNamed" msgid="415164730629690105">"Deschide cu %1$s"</string>
     <string name="whichViewApplicationLabel" msgid="7367556735684742409">"Deschide"</string>
@@ -1182,23 +1182,23 @@
     <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Deschide linkurile cu <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
     <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Deschide linkurile <xliff:g id="HOST">%1$s</xliff:g> cu <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
     <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Permite accesul"</string>
-    <string name="whichEditApplication" msgid="6191568491456092812">"Editați cu"</string>
-    <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editați cu %1$s"</string>
-    <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editați"</string>
+    <string name="whichEditApplication" msgid="6191568491456092812">"Editează cu"</string>
+    <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editează cu %1$s"</string>
+    <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editează"</string>
     <string name="whichSendApplication" msgid="4143847974460792029">"Trimite"</string>
-    <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Distribuiți cu %1$s"</string>
+    <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Distribuie cu %1$s"</string>
     <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Trimite"</string>
     <string name="whichSendToApplication" msgid="77101541959464018">"Trimite folosind"</string>
     <string name="whichSendToApplicationNamed" msgid="3385686512014670003">"Trimite folosind %1$s"</string>
     <string name="whichSendToApplicationLabel" msgid="3543240188816513303">"Trimite"</string>
     <string name="whichHomeApplication" msgid="8276350727038396616">"Selectează o aplicație de pe ecranul de pornire"</string>
-    <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"Utilizați %1$s ca ecran de pornire"</string>
-    <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Fotografiați"</string>
-    <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Fotografiați cu"</string>
-    <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Fotografiați cu %1$s"</string>
-    <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Fotografiați"</string>
+    <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"Folosește %1$s ca ecran de pornire"</string>
+    <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Fotografiază"</string>
+    <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Fotografiază cu"</string>
+    <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Fotografiază cu %1$s"</string>
+    <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Fotografiază"</string>
     <string name="alwaysUse" msgid="3153558199076112903">"Se utilizează în mod prestabilit pentru această acțiune."</string>
-    <string name="use_a_different_app" msgid="4987790276170972776">"Utilizați altă aplicație"</string>
+    <string name="use_a_different_app" msgid="4987790276170972776">"Folosește altă aplicație"</string>
     <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Șterge setările prestabilite din Setări de sistem &gt; Aplicații &gt; Descărcate."</string>
     <string name="chooseActivity" msgid="8563390197659779956">"Alege o acțiune"</string>
     <string name="chooseUsbActivity" msgid="2096269989990986612">"Alege o aplicație pentru dispozitivul USB"</string>
@@ -1210,8 +1210,8 @@
     <string name="aerr_restart" msgid="2789618625210505419">"Deschide din nou aplicația"</string>
     <string name="aerr_report" msgid="3095644466849299308">"Trimite feedback"</string>
     <string name="aerr_close" msgid="3398336821267021852">"Închide"</string>
-    <string name="aerr_mute" msgid="2304972923480211376">"Dezactivați până la repornirea dispozitivului"</string>
-    <string name="aerr_wait" msgid="3198677780474548217">"Așteptați"</string>
+    <string name="aerr_mute" msgid="2304972923480211376">"Dezactivează până la repornirea dispozitivului"</string>
+    <string name="aerr_wait" msgid="3198677780474548217">"Așteaptă"</string>
     <string name="aerr_close_app" msgid="8318883106083050970">"Închide aplicația"</string>
     <string name="anr_title" msgid="7290329487067300120"></string>
     <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> nu răspunde"</string>
@@ -1219,15 +1219,15 @@
     <string name="anr_application_process" msgid="4978772139461676184">"<xliff:g id="APPLICATION">%1$s</xliff:g> nu răspunde"</string>
     <string name="anr_process" msgid="1664277165911816067">"Procesul <xliff:g id="PROCESS">%1$s</xliff:g> nu răspunde"</string>
     <string name="force_close" msgid="9035203496368973803">"OK"</string>
-    <string name="report" msgid="2149194372340349521">"Raportați"</string>
-    <string name="wait" msgid="7765985809494033348">"Așteptați"</string>
-    <string name="webpage_unresponsive" msgid="7850879412195273433">"Pagina a devenit inactivă.\n\nDoriți să o închideți?"</string>
+    <string name="report" msgid="2149194372340349521">"Raportează"</string>
+    <string name="wait" msgid="7765985809494033348">"Așteaptă"</string>
+    <string name="webpage_unresponsive" msgid="7850879412195273433">"Pagina a devenit inactivă.\n\nO închizi?"</string>
     <string name="launch_warning_title" msgid="6725456009564953595">"Aplicație redirecționată"</string>
     <string name="launch_warning_replace" msgid="3073392976283203402">"<xliff:g id="APP_NAME">%1$s</xliff:g> funcționează acum."</string>
     <string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost lansată inițial."</string>
     <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Scară"</string>
     <string name="screen_compat_mode_show" msgid="5080361367584709857">"Afișează întotdeauna"</string>
-    <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reactivați acest mod din Setări de sistem &gt; Aplicații &gt; Descărcate."</string>
+    <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reactivează acest mod din Setări de sistem &gt; Aplicații &gt; Descărcate."</string>
     <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu acceptă setarea actuală pentru Dimensiunea afișării și este posibil să aibă un comportament neașteptat."</string>
     <string name="unsupported_display_size_show" msgid="980129850974919375">"Afișează întotdeauna"</string>
     <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost concepută pentru o versiune incompatibilă de sistem de operare Android și este posibil să se comporte în mod neprevăzut. Poate fi disponibilă o versiune actualizată a aplicației."</string>
@@ -1249,27 +1249,27 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Se pregătește <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Se pornesc aplicațiile."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Se finalizează pornirea."</string>
-    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ați apăsat butonul de pornire. De obicei, această acțiune dezactivează ecranul.\n\nAtingeți ușor când vă configurați amprenta."</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ai apăsat butonul de pornire. De obicei, astfel se dezactivează ecranul.\n\nAtinge ușor când îți configurezi amprenta."</string>
     <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Ca să termini configurarea, dezactivează ecranul"</string>
     <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Dezactivează"</string>
-    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuați cu verificarea amprentei?"</string>
-    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ați apăsat butonul de pornire. De obicei, această acțiune dezactivează ecranul.\n\nAtingeți ușor pentru verificarea amprentei."</string>
-    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Dezactivați ecranul"</string>
-    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuați"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continui cu verificarea amprentei?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ai apăsat butonul de pornire. De obicei, astfel se dezactivează ecranul.\n\nAtinge ușor pentru verificarea amprentei."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Dezactivează ecranul"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuă"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Rulează <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Atinge pentru a reveni la joc"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Alege jocul"</string>
     <string name="heavy_weight_switcher_text" msgid="6814316627367160126">"Pentru o performanță mai bună, se poate deschide un singur joc odată."</string>
-    <string name="old_app_action" msgid="725331621042848590">"Reveniți la <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+    <string name="old_app_action" msgid="725331621042848590">"Revino la <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
     <string name="new_app_action" msgid="547772182913269801">"Deschide <xliff:g id="NEW_APP">%1$s</xliff:g>"</string>
     <string name="new_app_description" msgid="1958903080400806644">"<xliff:g id="OLD_APP">%1$s</xliff:g> se va închide fără să salveze"</string>
     <string name="dump_heap_notification" msgid="5316644945404825032">"<xliff:g id="PROC">%1$s</xliff:g> a depășit limita de memorie"</string>
     <string name="dump_heap_ready_notification" msgid="2302452262927390268">"Datele privind memoria heap <xliff:g id="PROC">%1$s</xliff:g> sunt gata"</string>
     <string name="dump_heap_notification_detail" msgid="8431586843001054050">"Datele privind memoria au fost culese. Atinge pentru a trimite."</string>
     <string name="dump_heap_title" msgid="4367128917229233901">"Trimiți datele privind memoria?"</string>
-    <string name="dump_heap_text" msgid="1692649033835719336">"Procesul <xliff:g id="PROC">%1$s</xliff:g> și-a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le puteți trimite dezvoltatorului. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal la care aplicația are acces."</string>
-    <string name="dump_heap_system_text" msgid="6805155514925350849">"Procesul <xliff:g id="PROC">%1$s</xliff:g> a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le puteți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastați."</string>
-    <string name="dump_heap_ready_text" msgid="5849618132123045516">"Sunt disponibile datele privind memoria heap a procesului <xliff:g id="PROC">%1$s</xliff:g>, pe care le puteți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastați."</string>
+    <string name="dump_heap_text" msgid="1692649033835719336">"Procesul <xliff:g id="PROC">%1$s</xliff:g> și-a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le poți trimite dezvoltatorului. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal la care aplicația are acces."</string>
+    <string name="dump_heap_system_text" msgid="6805155514925350849">"Procesul <xliff:g id="PROC">%1$s</xliff:g> a depășit limita de memorie de <xliff:g id="SIZE">%2$s</xliff:g>. Sunt disponibile datele privind memoria heap, pe care le poți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastezi."</string>
+    <string name="dump_heap_ready_text" msgid="5849618132123045516">"Sunt disponibile datele privind memoria heap a procesului <xliff:g id="PROC">%1$s</xliff:g>, pe care le poți distribui. Atenție: aceste date privind memoria heap pot conține informații cu caracter personal sensibile la care procesul are acces și care pot include ceea ce tastezi."</string>
     <string name="sendText" msgid="493003724401350724">"Alege o acțiune pentru text"</string>
     <string name="volume_ringtone" msgid="134784084629229029">"Volum sonerie"</string>
     <string name="volume_music" msgid="7727274216734955095">"Volum media"</string>
@@ -1292,8 +1292,8 @@
     <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Sunete de alarmă"</string>
     <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Sunete pentru notificare"</string>
     <string name="ringtone_unknown" msgid="5059495249862816475">"Necunoscut"</string>
-    <string name="wifi_available_sign_in" msgid="381054692557675237">"Conectați-vă la rețeaua Wi-Fi"</string>
-    <string name="network_available_sign_in" msgid="1520342291829283114">"Conectați-vă la rețea"</string>
+    <string name="wifi_available_sign_in" msgid="381054692557675237">"Conectează-te la rețeaua Wi-Fi"</string>
+    <string name="network_available_sign_in" msgid="1520342291829283114">"Conectează-te la rețea"</string>
     <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
     <skip />
     <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
@@ -1302,7 +1302,7 @@
     <string name="other_networks_no_internet" msgid="6698711684200067033">"Rețeaua nu are acces la internet"</string>
     <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Serverul DNS privat nu poate fi accesat"</string>
     <string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> are conectivitate limitată"</string>
-    <string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Atingeți pentru a vă conecta oricum"</string>
+    <string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Atinge pentru a te conecta oricum"</string>
     <string name="network_switch_metered" msgid="1531869544142283384">"S-a comutat la <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
     <string name="network_switch_metered_detail" msgid="1358296010128405906">"Dispozitivul folosește <xliff:g id="NEW_NETWORK">%1$s</xliff:g> când <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nu are acces la internet. Se pot aplica taxe."</string>
     <string name="network_switch_metered_toast" msgid="501662047275723743">"S-a comutat de la <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> la <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
@@ -1314,7 +1314,7 @@
     <item msgid="9177085807664964627">"VPN"</item>
   </string-array>
     <string name="network_switch_type_name_unknown" msgid="3665696841646851068">"un tip de rețea necunoscut"</string>
-    <string name="accept" msgid="5447154347815825107">"Acceptați"</string>
+    <string name="accept" msgid="5447154347815825107">"Accept"</string>
     <string name="decline" msgid="6490507610282145874">"Refuz"</string>
     <string name="select_character" msgid="3352797107930786979">"Introdu caracterul"</string>
     <string name="sms_control_title" msgid="4748684259903148341">"Se trimit mesaje SMS"</string>
@@ -1322,29 +1322,29 @@
     <string name="sms_control_yes" msgid="4858845109269524622">"Permite"</string>
     <string name="sms_control_no" msgid="4845717880040355570">"Refuz"</string>
     <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; intenționează să trimită un mesaj la &lt;b&gt;<xliff:g id="DEST_ADDRESS">%2$s</xliff:g>&lt;/b&gt;."</string>
-    <string name="sms_short_code_details" msgid="2723725738333388351">"Acest lucru "<b>"poate genera costuri"</b>" în contul dvs. mobil."</string>
-    <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Acest lucru va genera costuri în contul dvs. mobil."</b></string>
+    <string name="sms_short_code_details" msgid="2723725738333388351">"Acest lucru "<b>"poate genera costuri"</b>" în contul tău mobil."</string>
+    <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Acest lucru va genera costuri în contul tău mobil."</b></string>
     <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Trimite"</string>
     <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Anulează"</string>
-    <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Doresc să se rețină opțiunea"</string>
-    <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Puteți modifica ulterior în Setări &gt; Aplicații"</string>
+    <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Reține opțiunea"</string>
+    <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Poți modifica ulterior în Setări &gt; Aplicații"</string>
     <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Permite întotdeauna"</string>
-    <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nu permiteți niciodată"</string>
+    <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nu permite niciodată"</string>
     <string name="sim_removed_title" msgid="5387212933992546283">"Card SIM eliminat"</string>
-    <string name="sim_removed_message" msgid="9051174064474904617">"Rețeaua mobilă va fi indisponibilă până când reporniți cu un card SIM valid introdus."</string>
+    <string name="sim_removed_message" msgid="9051174064474904617">"Rețeaua mobilă va fi indisponibilă până când repornești cu un card SIM valid introdus."</string>
     <string name="sim_done_button" msgid="6464250841528410598">"Terminat"</string>
     <string name="sim_added_title" msgid="7930779986759414595">"Card SIM adăugat"</string>
     <string name="sim_added_message" msgid="6602906609509958680">"Repornește dispozitivul pentru a accesa rețeaua mobilă."</string>
     <string name="sim_restart_button" msgid="8481803851341190038">"Repornește"</string>
-    <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activați serviciul mobil"</string>
-    <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Descărcați aplicația operatorului pentru a vă activa noul card SIM"</string>
-    <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Descărcați aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> pentru a vă activa noul card SIM"</string>
-    <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Descărcați aplicația"</string>
+    <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activează serviciul mobil"</string>
+    <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Descarcă aplicația operatorului pentru a activa noul card SIM"</string>
+    <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Descarcă aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> pentru a-ți activa noul card SIM"</string>
+    <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Descarcă aplicația"</string>
     <string name="carrier_app_notification_title" msgid="5815477368072060250">"S-a introdus un card SIM nou"</string>
     <string name="carrier_app_notification_text" msgid="6567057546341958637">"Atinge pentru a-l configura"</string>
-    <string name="time_picker_dialog_title" msgid="9053376764985220821">"Setați ora"</string>
-    <string name="date_picker_dialog_title" msgid="5030520449243071926">"Setați data"</string>
-    <string name="date_time_set" msgid="4603445265164486816">"Setați"</string>
+    <string name="time_picker_dialog_title" msgid="9053376764985220821">"Setează ora"</string>
+    <string name="date_picker_dialog_title" msgid="5030520449243071926">"Setează data"</string>
+    <string name="date_time_set" msgid="4603445265164486816">"Setează"</string>
     <string name="date_time_done" msgid="8363155889402873463">"Terminat"</string>
     <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NOU: "</font></string>
     <string name="perms_description_app" msgid="2747752389870161996">"Furnizată de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
@@ -1369,21 +1369,21 @@
     <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Atinge pentru a dezactiva remedierea erorilor wireless"</string>
     <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Selectează pentru a dezactiva remedierea erorilor wireless."</string>
     <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Modul Set de testare este activat"</string>
-    <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Reveniți la setările din fabrică pentru a dezactiva modul Set de testare."</string>
+    <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Revino la setările din fabrică pentru a dezactiva modul Set de testare."</string>
     <string name="console_running_notification_title" msgid="6087888939261635904">"Consola din serie este activată"</string>
-    <string name="console_running_notification_message" msgid="7892751888125174039">"Performanța este afectată. Pentru a dezactiva, verificați programul bootloader."</string>
+    <string name="console_running_notification_message" msgid="7892751888125174039">"Performanța este afectată. Pentru a dezactiva, verifică programul bootloader."</string>
     <string name="mte_override_notification_title" msgid="4731115381962792944">"MTE experimentală activată"</string>
-    <string name="mte_override_notification_message" msgid="2441170442725738942">"Performanța și stabilitatea pot fi afectate. Reporniți pentru a dezactiva. Dacă s-a activat cu arm64.memtag.bootctl, setați înainte la niciuna."</string>
+    <string name="mte_override_notification_message" msgid="2441170442725738942">"Performanța și stabilitatea pot fi afectate. Repornește pentru a dezactiva. Dacă s-a activat cu arm64.memtag.bootctl, setează dinainte la niciuna."</string>
     <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Lichide sau reziduuri în portul USB"</string>
-    <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"Portul USB este dezactivat automat. Atingeți ca să aflați mai multe."</string>
+    <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"Portul USB este dezactivat automat. Atinge ca să afli mai multe."</string>
     <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"Portul USB poate fi folosit"</string>
     <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Telefonul nu mai detectează lichide sau reziduuri."</string>
     <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Se creează un raport de eroare…"</string>
     <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Trimiți raportul de eroare?"</string>
     <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Se trimite raportul de eroare…"</string>
-    <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Administratorul dvs. a solicitat un raport de eroare pentru a remedia problemele acestui dispozitiv. Este posibil să se permită accesul la date și aplicații."</string>
-    <string name="share_remote_bugreport_action" msgid="7630880678785123682">"TRIMITEȚI"</string>
-    <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUZAȚI"</string>
+    <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Administratorul a solicitat un raport de eroare pentru a remedia problemele acestui dispozitiv. E posibil să se permită accesul la date și aplicații."</string>
+    <string name="share_remote_bugreport_action" msgid="7630880678785123682">"TRIMITE"</string>
+    <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>
@@ -1394,8 +1394,8 @@
     <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"Afișare peste alte aplicații"</string>
     <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> se afișează peste alte aplicații"</string>
     <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> se afișează peste aplicații"</string>
-    <string name="alert_windows_notification_message" msgid="6538171456970725333">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string>
-    <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Dezactivați"</string>
+    <string name="alert_windows_notification_message" msgid="6538171456970725333">"Dacă nu vrei ca <xliff:g id="NAME">%s</xliff:g> să folosească această funcție, atinge pentru a deschide setările și dezactiveaz-o."</string>
+    <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Dezactivează"</string>
     <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Se verifică <xliff:g id="NAME">%s</xliff:g>…"</string>
     <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Se examinează conținutul curent"</string>
     <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Se analizează spațiul de stocare media"</string>
@@ -1403,37 +1403,37 @@
     <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> nu funcționează"</string>
     <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Atinge pentru a configura"</string>
     <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Selectează pentru a configura"</string>
-    <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Poate fi nevoie să reformatați dispozitivul. Atingeți pentru a-l scoate."</string>
+    <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Poate fi nevoie să reformatezi dispozitivul. Atinge pentru a-l scoate."</string>
     <string name="ext_media_ready_notification_message" msgid="7509496364380197369">"Pentru stocarea de fotografii, videoclipuri, muzică și altele"</string>
-    <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"Răsfoiți fișierele media"</string>
+    <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"Răsfoiește fișierele media"</string>
     <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"Problemă cu <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> nu funcționează"</string>
     <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"Atinge pentru a remedia"</string>
     <string name="ext_media_unmountable_notification_message" product="tv" msgid="3003611129979934633">"<xliff:g id="NAME">%s</xliff:g> este corupt. Selectează pentru a remedia."</string>
-    <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Poate fi nevoie să reformatați dispozitivul. Atingeți pentru a-l scoate."</string>
+    <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Poate fi nevoie să reformatezi dispozitivul. Atinge pentru a-l scoate."</string>
     <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"S-a detectat <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> nu funcționează"</string>
     <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Atinge pentru a configura"</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Selectează pentru a configura <xliff:g id="NAME">%s</xliff:g> într-un format acceptat."</string>
-    <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Poate fi nevoie să reformatați dispozitivul"</string>
+    <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Poate fi nevoie să reformatezi dispozitivul"</string>
     <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"<xliff:g id="NAME">%s</xliff:g> scos pe neașteptate"</string>
     <string name="ext_media_badremoval_notification_message" msgid="1986514704499809244">"Deconectează din setări dispozitivele media înainte de a le îndepărta, pentru a evita pierderea conținutului"</string>
     <string name="ext_media_nomedia_notification_title" msgid="742671636376975890">"S-a eliminat <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Funcționarea ar putea fi necorespunzătoare. Introdu un dispozitiv de stocare nou."</string>
     <string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Se deconectează <xliff:g id="NAME">%s</xliff:g>"</string>
-    <string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Nu scoateți"</string>
+    <string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Nu scoate"</string>
     <string name="ext_media_init_action" msgid="2312974060585056709">"Configurează"</string>
     <string name="ext_media_unmount_action" msgid="966992232088442745">"Scoate"</string>
-    <string name="ext_media_browse_action" msgid="344865351947079139">"Explorați"</string>
-    <string name="ext_media_seamless_action" msgid="8837030226009268080">"Schimbați ieșirea"</string>
+    <string name="ext_media_browse_action" msgid="344865351947079139">"Explorează"</string>
+    <string name="ext_media_seamless_action" msgid="8837030226009268080">"Schimbă ieșirea"</string>
     <string name="ext_media_missing_title" msgid="3209472091220515046">"<xliff:g id="NAME">%s</xliff:g> lipsește"</string>
-    <string name="ext_media_missing_message" msgid="4408988706227922909">"Reintroduceți dispozitivul"</string>
+    <string name="ext_media_missing_message" msgid="4408988706227922909">"Reintrodu dispozitivul"</string>
     <string name="ext_media_move_specific_title" msgid="8492118544775964250">"Se mută <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_move_title" msgid="2682741525619033637">"Se mută datele"</string>
     <string name="ext_media_move_success_title" msgid="4901763082647316767">"Transfer de conținut încheiat"</string>
     <string name="ext_media_move_success_message" msgid="9159542002276982979">"Conținut mutat pe <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_move_failure_title" msgid="3184577479181333665">"Nu s-a putut muta conținutul"</string>
-    <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Încercați să mutați din nou conținutul"</string>
+    <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Încearcă să muți din nou conținutul"</string>
     <string name="ext_media_status_removed" msgid="241223931135751691">"Eliminat"</string>
     <string name="ext_media_status_unmounted" msgid="8145812017295835941">"Scos"</string>
     <string name="ext_media_status_checking" msgid="159013362442090347">"Se verifică..."</string>
@@ -1460,16 +1460,16 @@
     <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite unei aplicații să vadă toate pachetele instalate."</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Apasă de două ori pentru a controla mărirea/micșorarea"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nu s-a putut adăuga widgetul."</string>
-    <string name="ime_action_go" msgid="5536744546326495436">"Accesați"</string>
+    <string name="ime_action_go" msgid="5536744546326495436">"Accesează"</string>
     <string name="ime_action_search" msgid="4501435960587287668">"Caută"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"Trimite"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"Înainte"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"Terminat"</string>
     <string name="ime_action_previous" msgid="6548799326860401611">"Înapoi"</string>
-    <string name="ime_action_default" msgid="8265027027659800121">"Executați"</string>
-    <string name="dial_number_using" msgid="6060769078933953531">"Formați numărul\nutilizând <xliff:g id="NUMBER">%s</xliff:g>"</string>
-    <string name="create_contact_using" msgid="6200708808003692594">"Creați contactul\nutilizând <xliff:g id="NUMBER">%s</xliff:g>"</string>
-    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Următoarele aplicații solicită permisiunea de a accesa contul dvs. acum și în viitor."</string>
+    <string name="ime_action_default" msgid="8265027027659800121">"Execută"</string>
+    <string name="dial_number_using" msgid="6060769078933953531">"Formează numărul\nfolosind <xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="create_contact_using" msgid="6200708808003692594">"Creează contactul\nutilizând <xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Următoarele aplicații solicită permisiunea de a-ți accesa contul acum și în viitor."</string>
     <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Permiți această solicitare?"</string>
     <string name="grant_permissions_header_text" msgid="3420736827804657201">"Solicitare de acces"</string>
     <string name="allow" msgid="6195617008611933762">"Permite"</string>
@@ -1477,13 +1477,13 @@
     <string name="permission_request_notification_title" msgid="1810025922441048273">"Permisiune solicitată"</string>
     <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Permisiune solicitată\npentru contul <xliff:g id="ACCOUNT">%s</xliff:g>."</string>
     <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Permisiune solicitată de <xliff:g id="APP">%1$s</xliff:g>\npentru contul <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
-    <string name="forward_intent_to_owner" msgid="4620359037192871015">"Utilizați această aplicație în afara profilului de serviciu"</string>
-    <string name="forward_intent_to_work" msgid="3620262405636021151">"Utilizați această aplicație în profilul de serviciu"</string>
+    <string name="forward_intent_to_owner" msgid="4620359037192871015">"Folosești această aplicație în afara profilului de serviciu"</string>
+    <string name="forward_intent_to_work" msgid="3620262405636021151">"Folosești această aplicație în profilul de serviciu"</string>
     <string name="input_method_binding_label" msgid="1166731601721983656">"Metodă de intrare"</string>
     <string name="sync_binding_label" msgid="469249309424662147">"Sincronizare"</string>
     <string name="accessibility_binding_label" msgid="1974602776545801715">"Accesibilitate"</string>
     <string name="wallpaper_binding_label" msgid="1197440498000786738">"Imagine de fundal"</string>
-    <string name="chooser_wallpaper" msgid="3082405680079923708">"Schimbați imaginea de fundal"</string>
+    <string name="chooser_wallpaper" msgid="3082405680079923708">"Schimbă imaginea de fundal"</string>
     <string name="notification_listener_binding_label" msgid="2702165274471499713">"Serviciu de citire a notificărilor"</string>
     <string name="vr_listener_binding_label" msgid="8013112996671206429">"Instrument de ascultare pentru RV"</string>
     <string name="condition_provider_service_binding_label" msgid="8490641013951857673">"Furnizor de condiții"</string>
@@ -1496,23 +1496,23 @@
     <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Conectat(ă) la rețeaua VPN activată permanent"</string>
     <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Deconectat de la rețeaua VPN activată permanent"</string>
     <string name="vpn_lockdown_error" msgid="4453048646854247947">"Nu s-a putut conecta la rețeaua VPN activată permanent"</string>
-    <string name="vpn_lockdown_config" msgid="8331697329868252169">"Modificați setările de rețea sau VPN"</string>
+    <string name="vpn_lockdown_config" msgid="8331697329868252169">"Modifică setările de rețea sau VPN"</string>
     <string name="upload_file" msgid="8651942222301634271">"Alege un fișier"</string>
     <string name="no_file_chosen" msgid="4146295695162318057">"Nu au fost găsite fișiere"</string>
     <string name="reset" msgid="3865826612628171429">"Resetează"</string>
     <string name="submit" msgid="862795280643405865">"Trimite"</string>
     <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Aplicația pentru condus rulează"</string>
-    <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Atingeți ca să ieșiți din aplicația pentru condus."</string>
+    <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Atinge ca să ieși din aplicația pentru condus."</string>
     <string name="back_button_label" msgid="4078224038025043387">"Înapoi"</string>
     <string name="next_button_label" msgid="6040209156399907780">"Înainte"</string>
     <string name="skip_button_label" msgid="3566599811326688389">"Omite"</string>
     <string name="no_matches" msgid="6472699895759164599">"Nicio potrivire"</string>
-    <string name="find_on_page" msgid="5400537367077438198">"Găsiți pe pagină"</string>
+    <string name="find_on_page" msgid="5400537367077438198">"Caută în pagină"</string>
     <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# potrivire}few{# din {total}}other{# din {total}}}"</string>
     <string name="action_mode_done" msgid="2536182504764803222">"Terminat"</string>
     <string name="progress_erasing" msgid="6891435992721028004">"Se șterge spațiul de stocare distribuit..."</string>
-    <string name="share" msgid="4157615043345227321">"Distribuiți"</string>
-    <string name="find" msgid="5015737188624767706">"Găsiți"</string>
+    <string name="share" msgid="4157615043345227321">"Distribuie"</string>
+    <string name="find" msgid="5015737188624767706">"Caută"</string>
     <string name="websearch" msgid="5624340204512793290">"Căutare pe web"</string>
     <string name="find_next" msgid="5341217051549648153">"Următorul rezultat"</string>
     <string name="find_previous" msgid="4405898398141275532">"Rezultatul anterior"</string>
@@ -1522,29 +1522,29 @@
     <string name="gpsVerifYes" msgid="3719843080744112940">"Da"</string>
     <string name="gpsVerifNo" msgid="1671201856091564741">"Nu"</string>
     <string name="sync_too_many_deletes" msgid="6999440774578705300">"Limita pentru ștergere a fost depășită"</string>
-    <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"Există <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g>   elemente șterse pentru <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, contul <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Ce doriți să faceți?"</string>
+    <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"Există <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g>   elemente șterse pentru <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, contul <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Ce vrei să faci?"</string>
     <string name="sync_really_delete" msgid="5657871730315579051">"Șterge elementele"</string>
     <string name="sync_undo_deletes" msgid="5786033331266418896">"Anulează aceste ștergeri"</string>
-    <string name="sync_do_nothing" msgid="4528734662446469646">"Nu trebuie să luați nicio măsură deocamdată"</string>
+    <string name="sync_do_nothing" msgid="4528734662446469646">"Nu trebuie să iei nicio măsură deocamdată"</string>
     <string name="choose_account_label" msgid="5557833752759831548">"Alege un cont"</string>
     <string name="add_account_label" msgid="4067610644298737417">"Adaugă un cont"</string>
     <string name="add_account_button_label" msgid="322390749416414097">"Adaugă un cont"</string>
-    <string name="number_picker_increment_button" msgid="7621013714795186298">"Creșteți"</string>
-    <string name="number_picker_decrement_button" msgid="5116948444762708204">"Reduceți"</string>
-    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> atingeți lung."</string>
-    <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Glisați în sus pentru a crește și în jos pentru a reduce."</string>
-    <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Creșteți valoarea pentru minute"</string>
-    <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Reduceți valoarea pentru minute"</string>
-    <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Creșteți valoarea pentru oră"</string>
-    <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Reduceți valoarea pentru oră"</string>
-    <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Setați valoarea PM"</string>
-    <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Setați valoarea AM"</string>
-    <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Creșteți valoarea pentru lună"</string>
-    <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Reduceți valoarea pentru lună"</string>
-    <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Creșteți valoarea pentru zi"</string>
-    <string name="date_picker_decrement_day_button" msgid="6840253837656637248">"Reduceți valoarea pentru zi"</string>
-    <string name="date_picker_increment_year_button" msgid="7608128783435372594">"Creșteți valoarea pentru an"</string>
-    <string name="date_picker_decrement_year_button" msgid="4102586521754172684">"Reduceți valoarea pentru an"</string>
+    <string name="number_picker_increment_button" msgid="7621013714795186298">"Mărește"</string>
+    <string name="number_picker_decrement_button" msgid="5116948444762708204">"Redu"</string>
+    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> atinge lung."</string>
+    <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Glisează în sus pentru a crește și în jos pentru a reduce."</string>
+    <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Mărește valoarea pentru minute"</string>
+    <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Redu valoarea pentru minute"</string>
+    <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Mărește valoarea pentru oră"</string>
+    <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Redu valoarea pentru oră"</string>
+    <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Setează valoarea PM"</string>
+    <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Setează valoarea AM"</string>
+    <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Mărește valoarea pentru lună"</string>
+    <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Redu valoarea pentru lună"</string>
+    <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Mărește valoarea pentru zi"</string>
+    <string name="date_picker_decrement_day_button" msgid="6840253837656637248">"Redu valoarea pentru zi"</string>
+    <string name="date_picker_increment_year_button" msgid="7608128783435372594">"Mărește valoarea pentru an"</string>
+    <string name="date_picker_decrement_year_button" msgid="4102586521754172684">"Redu valoarea pentru an"</string>
     <string name="date_picker_prev_month_button" msgid="3418694374017868369">"Luna trecută"</string>
     <string name="date_picker_next_month_button" msgid="4858207337779144840">"Luna viitoare"</string>
     <string name="keyboardview_keycode_alt" msgid="8997420058584292385">"Alt"</string>
@@ -1558,10 +1558,10 @@
     <string name="activitychooserview_choose_application_error" msgid="6937782107559241734">"Nu s-a putut lansa <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="shareactionprovider_share_with" msgid="2753089758467748982">"Permite accesul pentru"</string>
     <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Permite accesul pentru <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
-    <string name="content_description_sliding_handle" msgid="982510275422590757">"Mâner glisant. Atingeți și țineți apăsat."</string>
-    <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Glisați pentru a debloca."</string>
-    <string name="action_bar_home_description" msgid="1501655419158631974">"Navigați la ecranul de pornire"</string>
-    <string name="action_bar_up_description" msgid="6611579697195026932">"Navigați în sus"</string>
+    <string name="content_description_sliding_handle" msgid="982510275422590757">"Ghidaj glisant. Atinge și ține apăsat."</string>
+    <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Glisează pentru a debloca."</string>
+    <string name="action_bar_home_description" msgid="1501655419158631974">"Navighează la ecranul de pornire"</string>
+    <string name="action_bar_up_description" msgid="6611579697195026932">"Navighează în sus"</string>
     <string name="action_menu_overflow_description" msgid="4579536843510088170">"Mai multe opțiuni"</string>
     <string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s, %2$s"</string>
     <string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s, %2$s, %3$s"</string>
@@ -1571,19 +1571,19 @@
     <string name="storage_usb_drive" msgid="448030813201444573">"Unitate USB"</string>
     <string name="storage_usb_drive_label" msgid="6631740655876540521">"Unitate USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
     <string name="storage_usb" msgid="2391213347883616886">"Dsipozitiv de stocare USB"</string>
-    <string name="extract_edit_menu_button" msgid="63954536535863040">"Editați"</string>
+    <string name="extract_edit_menu_button" msgid="63954536535863040">"Editează"</string>
     <string name="data_usage_warning_title" msgid="9034893717078325845">"Avertisment pentru date"</string>
-    <string name="data_usage_warning_body" msgid="1669325367188029454">"Ați folosit <xliff:g id="APP">%s</xliff:g> din date"</string>
+    <string name="data_usage_warning_body" msgid="1669325367188029454">"Ai folosit <xliff:g id="APP">%s</xliff:g> din date"</string>
     <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"S-a atins limita de date mobile"</string>
-    <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Ați atins limita de date Wi-Fi"</string>
+    <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Ai atins limita de date Wi-Fi"</string>
     <string name="data_usage_limit_body" msgid="3567699582000085710">"Datele au fost întrerupte pentru restul ciclului"</string>
     <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Peste limita de date mobile"</string>
     <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Peste limita de date Wi-Fi"</string>
-    <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Ați depășit limita stabilită cu <xliff:g id="SIZE">%s</xliff:g>"</string>
+    <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Ai depășit limita stabilită cu <xliff:g id="SIZE">%s</xliff:g>"</string>
     <string name="data_usage_restricted_title" msgid="126711424380051268">"Datele de fundal restricționate"</string>
-    <string name="data_usage_restricted_body" msgid="5338694433686077733">"Atingeți ca să eliminați restricția."</string>
+    <string name="data_usage_restricted_body" msgid="5338694433686077733">"Atinge ca să elimini restricția."</string>
     <string name="data_usage_rapid_title" msgid="2950192123248740375">"Utilizare mare de date mobile"</string>
-    <string name="data_usage_rapid_body" msgid="3886676853263693432">"Aplicațiile dvs. au utilizat mai multe date decât de obicei"</string>
+    <string name="data_usage_rapid_body" msgid="3886676853263693432">"Aplicațiile au folosit mai multe date decât de obicei"</string>
     <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"<xliff:g id="APP">%s</xliff:g> a utilizat mai multe date decât de obicei"</string>
     <string name="ssl_certificate" msgid="5690020361307261997">"Certificat de securitate"</string>
     <string name="ssl_certificate_is_valid" msgid="7293675884598527081">"Certificatul este valid."</string>
@@ -1601,9 +1601,9 @@
     <string name="sha1_fingerprint" msgid="2339915142825390774">"Amprentă SHA-1:"</string>
     <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Afișează-le pe toate"</string>
     <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Alege activitatea"</string>
-    <string name="share_action_provider_share_with" msgid="1904096863622941880">"Distribuiți pentru"</string>
+    <string name="share_action_provider_share_with" msgid="1904096863622941880">"Distribuie pentru"</string>
     <string name="sending" msgid="206925243621664438">"Se trimite..."</string>
-    <string name="launchBrowserDefault" msgid="6328349989932924119">"Lansați browserul?"</string>
+    <string name="launchBrowserDefault" msgid="6328349989932924119">"Lansezi browserul?"</string>
     <string name="SetupCallDefault" msgid="5581740063237175247">"Accepți apelul?"</string>
     <string name="activity_resolver_use_always" msgid="5575222334666843269">"Întotdeauna"</string>
     <string name="activity_resolver_use_once" msgid="948462794469672658">"Numai o dată"</string>
@@ -1619,8 +1619,8 @@
     <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="8297563323032966831">"Ecran wireless"</string>
     <string name="media_route_button_content_description" msgid="2299223698196869956">"Trimite"</string>
-    <string name="media_route_chooser_title" msgid="6646594924991269208">"Conectați-vă la dispozitiv"</string>
-    <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Proiectați ecranul pe dispozitiv"</string>
+    <string name="media_route_chooser_title" msgid="6646594924991269208">"Conectează-te la dispozitiv"</string>
+    <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Proiectează ecranul pe dispozitiv"</string>
     <string name="media_route_chooser_searching" msgid="6119673534251329535">"Se caută dispozitive..."</string>
     <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Setări"</string>
     <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Deconectează-te"</string>
@@ -1638,69 +1638,69 @@
     <string name="kg_wrong_pattern" msgid="1342812634464179931">"Model greșit"</string>
     <string name="kg_wrong_password" msgid="2384677900494439426">"Parolă greșită"</string>
     <string name="kg_wrong_pin" msgid="3680925703673166482">"Cod PIN greșit"</string>
-    <string name="kg_pattern_instructions" msgid="8366024510502517748">"Desenați modelul"</string>
+    <string name="kg_pattern_instructions" msgid="8366024510502517748">"Desenează modelul"</string>
     <string name="kg_sim_pin_instructions" msgid="6479401489471690359">"Introdu codul PIN al cardului SIM"</string>
     <string name="kg_pin_instructions" msgid="7355933174673539021">"Introdu codul PIN"</string>
     <string name="kg_password_instructions" msgid="7179782578809398050">"Introdu parola"</string>
-    <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"Cardul SIM este acum dezactivat. Introduceți codul PUK pentru a continua. Contactați operatorul pentru mai multe detalii."</string>
+    <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"Cardul SIM este acum dezactivat. Introdu codul PUK pentru a continua. Contactează operatorul pentru mai multe detalii."</string>
     <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Introdu codul PIN dorit"</string>
-    <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirmați codul PIN dorit"</string>
+    <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirmă codul PIN dorit"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"Se deblochează cardul SIM..."</string>
     <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Cod PIN incorect."</string>
     <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Introdu un cod PIN format din 4 până la 8 cifre."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"Codul PUK trebuie să conțină 8 numere."</string>
-    <string name="kg_invalid_puk" msgid="4809502818518963344">"Reintroduceți codul PUK corect. Încercările repetate vor dezactiva definitiv cardul SIM."</string>
+    <string name="kg_invalid_puk" msgid="4809502818518963344">"Reintrodu codul PUK corect. Încercările repetate vor dezactiva definitiv cardul SIM."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"Codurile PIN nu coincid"</string>
     <string name="kg_login_too_many_attempts" msgid="699292728290654121">"Prea multe încercări de desenare a modelului"</string>
-    <string name="kg_login_instructions" msgid="3619844310339066827">"Pentru a debloca, conectați-vă cu Contul dvs. Google."</string>
+    <string name="kg_login_instructions" msgid="3619844310339066827">"Pentru a debloca, conectează-te folosind Contul Google."</string>
     <string name="kg_login_username_hint" msgid="1765453775467133251">"Nume de utilizator (e-mail)"</string>
     <string name="kg_login_password_hint" msgid="3330530727273164402">"Parolă"</string>
     <string name="kg_login_submit_button" msgid="893611277617096870">"Conectează-te"</string>
     <string name="kg_login_invalid_input" msgid="8292367491901220210">"Nume de utilizator sau parolă nevalide."</string>
-    <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"Ați uitat numele de utilizator sau parola?\nAccesați "<b>"google.com/accounts/recovery"</b>"."</string>
+    <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"Ai uitat numele de utilizator sau parola?\nAccesează "<b>"google.com/accounts/recovery"</b>"."</string>
     <string name="kg_login_checking_password" msgid="4676010303243317253">"Se verifică contul…"</string>
-    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g>   secunde."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va fi resetată la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va fi resetat la setările prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va fi acum resetată la setările prestabilite din fabrică."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string>
-    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Telefonul va fi acum resetat la setările prestabilite din fabrică."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g>   secunde."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați dispozitivul Android TV cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g>   secunde."</string>
+    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Ai introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Ai introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, aceasta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acesta va reveni la setările din fabrică, iar toate datele de utilizator se vor pierde."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Tableta va reveni acum la setările din fabrică."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a dispozitivului Android TV. Acesta va reveni la setările din fabrică."</string>
+    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Telefonul va reveni acum la setările din fabrică."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi tableta cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi dispozitivul Android TV cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi telefonul cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
-    <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eliminați"</string>
-    <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string>
-    <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string>
-    <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Atunci când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de trei secunde, veți lansa o funcție de accesibilitate."</string>
-    <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activați comanda rapidă pentru funcțiile de accesibilitate?"</string>
-    <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apăsați ambele taste de volum câteva secunde, activați funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuteți schimba funcțiile selectate din Setări &gt; Accesibilitate."</string>
+    <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Elimină"</string>
+    <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Mărești volumul peste nivelul recomandat?\n\nDacă asculți perioade lungi la volum ridicat, auzul poate fi afectat."</string>
+    <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Folosești comanda rapidă pentru accesibilitate?"</string>
+    <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Când comanda rapidă e activată, dacă apeși ambele butoane de volum timp de trei secunde, vei lansa o funcție de accesibilitate."</string>
+    <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activezi comanda rapidă pentru funcțiile de accesibilitate?"</string>
+    <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apeși ambele taste de volum câteva secunde, activezi funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPoți schimba funcțiile selectate din Setări &gt; Accesibilitate."</string>
     <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
-    <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activați comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
-    <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apăsați ambele taste de volum câteva secunde, activați funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPuteți alege altă funcție pentru această comandă în Setări &gt; Accesibilitate."</string>
-    <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activați"</string>
-    <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nu activați"</string>
+    <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activezi comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apeși ambele taste de volum câteva secunde, activezi funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPoți alege altă funcție pentru această comandă în Setări &gt; Accesibilitate."</string>
+    <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activează"</string>
+    <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nu activa"</string>
     <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"ACTIVAT"</string>
     <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"DEZACTIVAT"</string>
-    <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Permiteți serviciului <xliff:g id="SERVICE">%1$s</xliff:g> să aibă control total asupra dispozitivului dvs.?"</string>
-    <string name="accessibility_service_warning_description" msgid="291674995220940133">"Controlul total este adecvat pentru aplicații care vă ajută cu accesibilitatea, însă nu pentru majoritatea aplicaților."</string>
-    <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Vă vede și vă controlează ecranul"</string>
+    <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Permiți serviciului <xliff:g id="SERVICE">%1$s</xliff:g> să aibă control total asupra dispozitivului?"</string>
+    <string name="accessibility_service_warning_description" msgid="291674995220940133">"Controlul total este adecvat pentru aplicații care te ajută cu accesibilitatea, însă nu pentru majoritatea aplicaților."</string>
+    <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"să vadă și să controleze ecranul"</string>
     <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Poate citi tot conținutul de pe ecran și poate afișa conținut peste alte aplicații."</string>
-    <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Vă vede interacțiunile și le realizează"</string>
-    <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Poate urmări interacțiunile dvs. cu o aplicație sau cu un senzor hardware și poate interacționa cu aplicații în numele dvs."</string>
+    <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"să vadă și să facă acțiuni"</string>
+    <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Poate să urmărească interacțiunile tale cu o aplicație sau cu un senzor hardware și să interacționeze cu aplicații în numele tău."</string>
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permite"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuz"</string>
-    <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Atingeți o funcție ca să începeți să o folosiți:"</string>
-    <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Alegeți funcțiile pe care să le folosiți cu butonul de accesibilitate"</string>
+    <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Atinge o funcție ca să începi să o folosești:"</string>
+    <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Alege funcțiile pe care să le folosești cu butonul de accesibilitate"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Alege funcțiile pentru comanda rapidă a butonului de volum"</string>
     <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> a fost dezactivat"</string>
-    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editați comenzile rapide"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editează comenzile rapide"</string>
     <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Gata"</string>
-    <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Dezactivați comanda rapidă"</string>
-    <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizați comanda rapidă"</string>
+    <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Dezactivează comanda rapidă"</string>
+    <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Folosește comanda rapidă"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversarea culorilor"</string>
     <string name="color_correction_feature_name" msgid="3655077237805422597">"Corecția culorii"</string>
     <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modul cu o mână"</string>
@@ -1708,12 +1708,12 @@
     <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"S-au apăsat lung tastele de volum. S-a activat <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string>
     <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"S-au apăsat lung tastele de volum. S-a dezactivat <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Apasă ambele butoane de volum timp de trei secunde pentru a folosi <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Alegeți o funcție pe care să o folosiți când atingeți butonul de accesibilitate:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Alegeți o funcție pe care să o folosiți cu gestul de accesibilitate (glisați în sus cu două degete din partea de jos a ecranului):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Alegeți o funcție pe care să o folosiți cu gestul de accesibilitate (glisați în sus cu trei degete din partea de jos a ecranului):"</string>
-    <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Pentru a comuta între funcții, atingeți lung butonul de accesibilitate."</string>
-    <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Pentru a comuta între funcții, glisați în sus cu două degete și mențineți apăsat."</string>
-    <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Pentru a comuta între funcții, glisați în sus cu trei degete și mențineți apăsat."</string>
+    <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Alege o funcție pe care să o folosești când atingi butonul de accesibilitate:"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Alege o funcție pe care să o folosești cu gestul de accesibilitate (glisează în sus cu două degete din partea de jos a ecranului):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Alege o funcție pe care să o folosești cu gestul de accesibilitate (glisează în sus cu trei degete din partea de jos a ecranului):"</string>
+    <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Pentru a comuta între funcții, atinge lung butonul de accesibilitate."</string>
+    <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Pentru a comuta între funcții, glisează în sus cu două degete și ține apăsat."</string>
+    <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Pentru a comuta între funcții, glisează în sus cu trei degete și ține apăsat."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Mărire"</string>
     <string name="user_switched" msgid="7249833311585228097">"Utilizator curent: <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="user_switching_message" msgid="1912993630661332336">"Se comută la <xliff:g id="NAME">%1$s</xliff:g>…"</string>
@@ -1721,9 +1721,9 @@
     <string name="owner_name" msgid="8713560351570795743">"Proprietar"</string>
     <string name="guest_name" msgid="8502103277839834324">"Invitat"</string>
     <string name="error_message_title" msgid="4082495589294631966">"Eroare"</string>
-    <string name="error_message_change_not_allowed" msgid="843159705042381454">"Această modificare nu este permisă de administratorul dvs."</string>
+    <string name="error_message_change_not_allowed" msgid="843159705042381454">"Această modificare nu este permisă de administrator"</string>
     <string name="app_not_found" msgid="3429506115332341800">"Nicio aplicație pentru gestionarea acestei acțiuni"</string>
-    <string name="revoke" msgid="5526857743819590458">"Revocați"</string>
+    <string name="revoke" msgid="5526857743819590458">"Revocă"</string>
     <string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
     <string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string>
     <string name="mediasize_iso_a2" msgid="2779860175680233980">"ISO A2"</string>
@@ -1830,13 +1830,13 @@
     <string name="restr_pin_incorrect" msgid="3861383632940852496">"Incorect"</string>
     <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Codul PIN actual"</string>
     <string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"Codul PIN nou"</string>
-    <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirmați noul cod PIN"</string>
-    <string name="restr_pin_create_pin" msgid="917067613896366033">"Creați un cod PIN pentru modificarea restricțiilor"</string>
-    <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"Codurile PIN nu se potrivesc. Încercați din nou."</string>
+    <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirmă noul cod PIN"</string>
+    <string name="restr_pin_create_pin" msgid="917067613896366033">"Creează un cod PIN pentru modificarea restricțiilor"</string>
+    <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PIN-urile nu sunt identice. Încearcă din nou."</string>
     <string name="restr_pin_error_too_short" msgid="1547007808237941065">"Codul PIN este prea scurt. Trebuie să aibă cel puțin 4 cifre."</string>
     <string name="restr_pin_try_later" msgid="5897719962541636727">"Reîncearcă mai târziu"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Vizualizare pe ecran complet"</string>
-    <string name="immersive_cling_description" msgid="7092737175345204832">"Pentru a ieși, glisați de sus în jos."</string>
+    <string name="immersive_cling_description" msgid="7092737175345204832">"Pentru a ieși, glisează de sus în jos."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Am înțeles"</string>
     <string name="done_label" msgid="7283767013231718521">"Terminat"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Selector circular pentru ore"</string>
@@ -1852,15 +1852,15 @@
     <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicită codul PIN înainte de a anula fixarea"</string>
     <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicită mai întâi modelul pentru deblocare"</string>
     <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicită parola înainte de a anula fixarea"</string>
-    <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalat de administratorul dvs."</string>
-    <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administratorul dvs."</string>
-    <string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administratorul dvs."</string>
+    <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalat de administrator"</string>
+    <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administrator"</string>
+    <string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administrator"</string>
     <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
     <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string>
     <string name="battery_saver_description" msgid="8518809702138617167">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string>
-    <string name="data_saver_description" msgid="4995164271550590517">"Pentru a contribui la reducerea utilizării de date, Economizorul de date împiedică unele aplicații să trimită sau să primească date în fundal. O aplicație pe care o folosiți poate accesa datele, însă mai rar. Aceasta poate însemna, de exemplu, că imaginile se afișează numai după ce le atingeți."</string>
-    <string name="data_saver_enable_title" msgid="7080620065745260137">"Activați Economizorul de date?"</string>
-    <string name="data_saver_enable_button" msgid="4399405762586419726">"Activați"</string>
+    <string name="data_saver_description" msgid="4995164271550590517">"Pentru a contribui la reducerea utilizării de date, Economizorul de date împiedică unele aplicații să trimită sau să primească date în fundal. O aplicație pe care o folosești poate accesa datele, însă mai rar. Aceasta poate însemna, de exemplu, că imaginile se afișează numai după ce le atingi."</string>
+    <string name="data_saver_enable_title" msgid="7080620065745260137">"Activezi Economizorul de date?"</string>
+    <string name="data_saver_enable_button" msgid="4399405762586419726">"Activează"</string>
     <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Timp de un minut (până la {formattedTime})}few{Timp de # minute (până la {formattedTime})}other{Timp de # de minute (până la {formattedTime})}}"</string>
     <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Timp de un min. (până la {formattedTime})}few{Timp de # min. (până la {formattedTime})}other{Timp de # min. (până la {formattedTime})}}"</string>
     <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{Timp de o oră (până la {formattedTime})}few{Timp de # ore (până la {formattedTime})}other{Timp de # de ore (până la {formattedTime})}}"</string>
@@ -1872,8 +1872,8 @@
     <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Până <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_until" msgid="2250286190237669079">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_alarm" msgid="7046911727540499275">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (următoarea alarmă)"</string>
-    <string name="zen_mode_forever" msgid="740585666364912448">"Până când dezactivați"</string>
-    <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Până când dezactivați „Nu deranja”"</string>
+    <string name="zen_mode_forever" msgid="740585666364912448">"Până dezactivezi"</string>
+    <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Până când dezactivezi „Nu deranja”"</string>
     <string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
     <string name="toolbar_collapse_description" msgid="8009920446193610996">"Restrânge"</string>
     <string name="zen_mode_feature_name" msgid="3785547207263754500">"Nu deranja"</string>
@@ -1884,7 +1884,7 @@
     <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Somn"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string>
-    <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactați producătorul."</string>
+    <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactează producătorul."</string>
     <string name="stk_cc_ussd_to_dial" msgid="3139884150741157610">"Solicitarea USSD a fost schimbată cu un apel obișnuit"</string>
     <string name="stk_cc_ussd_to_ss" msgid="4826846653052609738">"Solicitarea USSD a fost schimbată cu o solicitare SS"</string>
     <string name="stk_cc_ussd_to_ussd" msgid="8343001461299302472">"Schimbat cu o solicitare USSD nouă"</string>
@@ -1899,24 +1899,24 @@
     <string name="notification_verified_content_description" msgid="6401483602782359391">"Confirmat"</string>
     <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Extinde"</string>
     <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Restrânge"</string>
-    <string name="expand_action_accessibility" msgid="1947657036871746627">"extindeți/restrângeți"</string>
+    <string name="expand_action_accessibility" msgid="1947657036871746627">"extinde/restrânge"</string>
     <string name="usb_midi_peripheral_name" msgid="490523464968655741">"Port USB Android periferic"</string>
     <string name="usb_midi_peripheral_manufacturer_name" msgid="7557148557088787741">"Android"</string>
     <string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"Port USB periferic"</string>
     <string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"Mai multe opțiuni"</string>
     <string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Închide meniul suplimentar"</string>
-    <string name="maximize_button_text" msgid="4258922519914732645">"Maximizați"</string>
+    <string name="maximize_button_text" msgid="4258922519914732645">"Maximizează"</string>
     <string name="close_button_text" msgid="10603510034455258">"Închide"</string>
     <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
-    <string name="call_notification_answer_action" msgid="5999246836247132937">"Răspundeți"</string>
+    <string name="call_notification_answer_action" msgid="5999246836247132937">"Răspunde"</string>
     <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string>
-    <string name="call_notification_decline_action" msgid="3700345945214000726">"Respingeți"</string>
-    <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Încheiați"</string>
+    <string name="call_notification_decline_action" msgid="3700345945214000726">"Respinge"</string>
+    <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Închide"</string>
     <string name="call_notification_incoming_text" msgid="6143109825406638201">"Apel primit"</string>
     <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Apel în desfășurare"</string>
     <string name="call_notification_screening_text" msgid="8396931408268940208">"Se filtrează un apel primit"</string>
     <string name="default_notification_channel_label" msgid="3697928973567217330">"Neclasificate"</string>
-    <string name="importance_from_user" msgid="2782756722448800447">"Dvs. setați importanța acestor notificări."</string>
+    <string name="importance_from_user" msgid="2782756722448800447">"Tu setezi importanța acestor notificări."</string>
     <string name="importance_from_person" msgid="4235804979664465383">"Notificarea este importantă având în vedere persoanele implicate."</string>
     <string name="notification_history_title_placeholder" msgid="7748630986182249599">"Notificare de aplicație personalizată"</string>
     <string name="user_creation_account_exists" msgid="2239146360099708035">"Permiți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>? (există deja un utilizator cu acest cont)"</string>
@@ -1936,15 +1936,15 @@
     <string name="app_suspended_default_message" msgid="6451215678552004172">"Momentan, aplicația <xliff:g id="APP_NAME_0">%1$s</xliff:g> nu este disponibilă. Aceasta este gestionată de <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"Află mai multe"</string>
     <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Anulează întreruperea aplicației"</string>
-    <string name="work_mode_off_title" msgid="961171256005852058">"Activați aplicațiile pentru lucru?"</string>
-    <string name="work_mode_off_message" msgid="7319580997683623309">"Obțineți acces la aplicațiile pentru lucru și notificări"</string>
-    <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string>
+    <string name="work_mode_off_title" msgid="961171256005852058">"Activezi aplicațiile pentru lucru?"</string>
+    <string name="work_mode_off_message" msgid="7319580997683623309">"Obține acces la aplicațiile și notificările pentru lucru"</string>
+    <string name="work_mode_turn_on" msgid="3662561662475962285">"Activează"</string>
     <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_camera_dialog" msgid="3935701653713853065">"Camera video nu este disponibilă"</string>
-    <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuați pe telefon"</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>
     <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Aplicația Magazin Play nu este disponibilă"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Setările pentru Android TV sunt indisponibile"</string>
@@ -1959,17 +1959,17 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Nu se poate accesa pe <xliff:g id="DEVICE">%1$s</xliff:g>. Încearcă pe dispozitivul Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Nu se poate accesa pe <xliff:g id="DEVICE">%1$s</xliff:g>. Încearcă pe tabletă."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Nu se poate accesa pe <xliff:g id="DEVICE">%1$s</xliff:g>. Încearcă pe telefon."</string>
-    <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string>
+    <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și e posibil să nu funcționeze corect. Încearcă să cauți actualizări sau contactează dezvoltatorul."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Caută actualizări"</string>
-    <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string>
-    <string name="new_sms_notification_content" msgid="3197949934153460639">"Deschideți aplicația pentru SMS-uri ca să vizualizați"</string>
+    <string name="new_sms_notification_title" msgid="6528758221319927107">"Ai mesaje noi"</string>
+    <string name="new_sms_notification_content" msgid="3197949934153460639">"Deschide aplicația pentru SMS-uri ca să vezi"</string>
     <string name="profile_encrypted_title" msgid="9001208667521266472">"Unele funcții ar putea fi limitate"</string>
     <string name="profile_encrypted_detail" msgid="5279730442756849055">"Profil de serviciu blocat"</string>
-    <string name="profile_encrypted_message" msgid="1128512616293157802">"Atingeți ca să deblocați"</string>
+    <string name="profile_encrypted_message" msgid="1128512616293157802">"Atinge ca să deblochezi"</string>
     <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectat la <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
     <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Atinge pentru a vedea fișierele"</string>
-    <string name="pin_target" msgid="8036028973110156895">"Fixați"</string>
-    <string name="pin_specific_target" msgid="7824671240625957415">"Fixați <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="pin_target" msgid="8036028973110156895">"Fixează"</string>
+    <string name="pin_specific_target" msgid="7824671240625957415">"Fixează <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="unpin_target" msgid="3963318576590204447">"Anulează fixarea"</string>
     <string name="unpin_specific_target" msgid="3859828252160908146">"Anulează fixarea pentru <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="app_info" msgid="6113278084877079851">"Informații despre aplicație"</string>
@@ -1992,11 +1992,11 @@
     <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Remedierea erorilor prin USB"</string>
     <string name="time_picker_hour_label" msgid="4208590187662336864">"oră"</string>
     <string name="time_picker_minute_label" msgid="8307452311269824553">"minut"</string>
-    <string name="time_picker_header_text" msgid="9073802285051516688">"Setați ora"</string>
+    <string name="time_picker_header_text" msgid="9073802285051516688">"Setează ora"</string>
     <string name="time_picker_input_error" msgid="8386271930742451034">"Introdu o oră validă"</string>
     <string name="time_picker_prompt_label" msgid="303588544656363889">"Introdu ora"</string>
-    <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Pentru a introduce ora, comutați la modul de introducere a textului."</string>
-    <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Pentru a introduce ora, comutați la modul ceas."</string>
+    <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Pentru a introduce ora, comută la modul de introducere a textului."</string>
+    <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Pentru a introduce ora, comută la modul ceas."</string>
     <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opțiuni de completare automată"</string>
     <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Salvează pentru completare automată"</string>
     <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Conținutul nu poate fi completat automat"</string>
@@ -2015,7 +2015,7 @@
     <string name="autofill_save_notnow" msgid="2853932672029024195">"Nu acum"</string>
     <string name="autofill_save_never" msgid="6821841919831402526">"Niciodată"</string>
     <string name="autofill_update_yes" msgid="4608662968996874445">"Actualizează"</string>
-    <string name="autofill_continue_yes" msgid="7914985605534510385">"Continuați"</string>
+    <string name="autofill_continue_yes" msgid="7914985605534510385">"Continuă"</string>
     <string name="autofill_save_type_password" msgid="5624528786144539944">"parolă"</string>
     <string name="autofill_save_type_address" msgid="3111006395818252885">"adresă"</string>
     <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"card de credit"</string>
@@ -2024,11 +2024,11 @@
     <string name="autofill_save_type_generic_card" msgid="1019367283921448608">"card"</string>
     <string name="autofill_save_type_username" msgid="1018816929884640882">"nume de utilizator"</string>
     <string name="autofill_save_type_email_address" msgid="1303262336895591924">"adresă de e-mail"</string>
-    <string name="etws_primary_default_message_earthquake" msgid="8401079517718280669">"Păstrați-vă calmul și căutați un adăpost în apropiere."</string>
-    <string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"Părăsiți imediat zonele de coastă și din apropierea râurilor și îndreptați-vă spre un loc mai sigur, cum ar fi o zonă aflată la înălțime."</string>
-    <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="4888224011071875068">"Păstrați-vă calmul și căutați un adăpost în apropiere."</string>
+    <string name="etws_primary_default_message_earthquake" msgid="8401079517718280669">"Păstrează-ți calmul și caută un adăpost în apropiere."</string>
+    <string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"Părăsește imediat zonele de coastă și din apropierea râurilor și îndreaptă-te spre un loc mai sigur, cum ar fi o zonă aflată la înălțime."</string>
+    <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="4888224011071875068">"Păstrează-ți calmul și caută un adăpost în apropiere."</string>
     <string name="etws_primary_default_message_test" msgid="4583367373909549421">"Testarea mesajelor de urgență"</string>
-    <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Răspundeți"</string>
+    <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Răspunde"</string>
     <string name="etws_primary_default_message_others" msgid="7958161706019130739"></string>
     <string name="mmcc_authentication_reject" msgid="4891965994643876369">"Cardul SIM nu este permis pentru voce"</string>
     <string name="mmcc_imsi_unknown_in_hlr" msgid="227760698553988751">"Cardul SIM nu este activat pentru voce"</string>
@@ -2045,26 +2045,27 @@
     <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Nu s-a putut restabili comanda rapidă din cauza nepotrivirii semnăturii aplicației"</string>
     <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Nu s-a putut restabili comanda rapidă"</string>
     <string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"Comanda rapidă este dezactivată"</string>
-    <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEZINSTALAȚI"</string>
+    <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEZINSTALEAZĂ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"Deschide oricum"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicație dăunătoare detectată"</string>
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permiți ca <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> să acceseze toate jurnalele dispozitivului?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permite accesul o dată"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permiteți"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string>
+    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permite"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Jurnalele dispozitivului înregistrează activitatea de pe acesta. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. E posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv.\n\nAflă mai multe la g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nu mai afișa"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vrea să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>"</string>
-    <string name="screenshot_edit" msgid="7408934887203689207">"Editați"</string>
+    <string name="screenshot_edit" msgid="7408934887203689207">"Editează"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Apelurile și notificările vor vibra"</string>
     <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Apelurile și notificările vor avea sunetul dezactivat"</string>
     <string name="notification_channel_system_changes" msgid="2462010596920209678">"Modificări de sistem"</string>
     <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Nu deranja"</string>
     <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"Funcția nouă Nu deranja ascunde notificările"</string>
-    <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Atingeți ca să aflați mai multe și să modificați"</string>
+    <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Atinge ca să afli mai multe și să modifici"</string>
     <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Funcția Nu deranja s-a schimbat"</string>
     <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Atinge pentru a verifica ce este blocat."</string>
-    <string name="review_notification_settings_title" msgid="5102557424459810820">"Examinați setările pentru notificări"</string>
-    <string name="review_notification_settings_text" msgid="5916244866751849279">"Începând cu Android 13, aplicațiile pe care le instalați necesită permisiunea de a trimite notificări. Atingeți ca să modificați permisiunea pentru aplicațiile existente."</string>
+    <string name="review_notification_settings_title" msgid="5102557424459810820">"Verifică setările pentru notificări"</string>
+    <string name="review_notification_settings_text" msgid="5916244866751849279">"Începând cu Android 13, aplicațiile pe care le instalezi necesită permisiunea de a trimite notificări. Atinge ca să modifici permisiunea pentru aplicațiile existente."</string>
     <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Mai târziu"</string>
     <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Închide"</string>
     <string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
@@ -2072,7 +2073,7 @@
     <string name="notification_appops_camera_active" msgid="8177643089272352083">"Cameră foto"</string>
     <string name="notification_appops_microphone_active" msgid="581333393214739332">"Microfon"</string>
     <string name="notification_appops_overlay_active" msgid="5571732753262836481">"se afișează peste alte aplicații de pe ecran"</string>
-    <string name="notification_feedback_indicator" msgid="663476517711323016">"Oferiți feedback"</string>
+    <string name="notification_feedback_indicator" msgid="663476517711323016">"Oferă feedback"</string>
     <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Notificarea a fost promovată la Prestabilită. Atinge pentru a oferi feedback."</string>
     <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Notificarea a fost mutată în jos la Silențioasă. Atinge pentru a oferi feedback."</string>
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Notificarea a fost mutată la un nivel superior. Atinge pentru a oferi feedback."</string>
@@ -2080,9 +2081,9 @@
     <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificări optimizate"</string>
     <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Acțiunile și răspunsurile sugerate sunt acum trimise prin notificări optimizate. Notificările adaptive Android nu mai sunt acceptate."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
-    <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Dezactivați"</string>
+    <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Dezactivează"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Află mai multe"</string>
-    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Notificările optimizate au înlocuit Notificările adaptive Android de pe Android 12. Această funcție afișează acțiuni și răspunsuri sugerate și vă organizează notificările.\n\nNotificările optimizate pot accesa conținutul notificărilor, inclusiv informații cu caracter personal, precum mesajele și numele persoanelor de contact. În plus, funcția poate să închidă sau să răspundă la notificări, de exemplu, să răspundă la apeluri telefonice și să gestioneze opțiunea Nu deranja."</string>
+    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Notificările optimizate au înlocuit Notificările adaptive Android de pe Android 12. Această funcție afișează acțiuni și răspunsuri sugerate și organizează notificările.\n\nNotificările optimizate pot accesa conținutul notificărilor, inclusiv informații cu caracter personal, precum mesajele și numele persoanelor de contact. În plus, funcția poate să închidă sau să răspundă la notificări, de exemplu, să răspundă la apeluri telefonice și să gestioneze opțiunea Nu deranja."</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificare pentru informații despre modul Rutină"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economisirea bateriei este activată"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Se reduce utilizarea bateriei pentru a-i extinde autonomia"</string>
@@ -2153,10 +2154,10 @@
     <string name="resolver_switch_on_work" msgid="463709043650610420">"Atinge pentru a activa"</string>
     <string name="resolver_no_work_apps_available" msgid="3298291360133337270">"Nicio aplicație pentru lucru"</string>
     <string name="resolver_no_personal_apps_available" msgid="6284837227019594881">"Nicio aplicație personală"</string>
-    <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Deschideți <xliff:g id="APP">%s</xliff:g> în profilul personal?"</string>
-    <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Deschideți <xliff:g id="APP">%s</xliff:g> în profilul de serviciu?"</string>
-    <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Folosiți browserul personal"</string>
-    <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Folosiți browserul de serviciu"</string>
+    <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Deschizi <xliff:g id="APP">%s</xliff:g> în profilul personal?"</string>
+    <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Deschizi <xliff:g id="APP">%s</xliff:g> în profilul de serviciu?"</string>
+    <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Folosește browserul personal"</string>
+    <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Folosește browserul de serviciu"</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY" msgid="8050953231914637819">"Codul PIN de deblocare SIM privind rețeaua"</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ENTRY" msgid="7164399703751688214">"Codul PIN de deblocare SIM privind subsetul de rețea"</string>
     <string name="PERSOSUBSTATE_SIM_CORPORATE_ENTRY" msgid="4447629474818217364">"Codul PIN de deblocare SIM corporativă"</string>
@@ -2270,18 +2271,18 @@
     <string name="config_pdp_reject_service_not_subscribed" msgid="8190338397128671588"></string>
     <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="6024904218067254186"></string>
     <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Noi setări de mărire"</string>
-    <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Acum puteți mări o parte a ecranului"</string>
-    <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activați din Setări"</string>
-    <string name="dismiss_action" msgid="1728820550388704784">"Respingeți"</string>
-    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Deblocați microfonul dispozitivului"</string>
-    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Deblocați camera dispozitivului"</string>
+    <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Acum poți mări o parte a ecranului"</string>
+    <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activează din Setări"</string>
+    <string name="dismiss_action" msgid="1728820550388704784">"Închide"</string>
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Deblochează microfonul dispozitivului"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Deblochează camera dispozitivului"</string>
     <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Pentru &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; și toate aplicațiile și serviciile"</string>
-    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Deblocați"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Deblochează"</string>
     <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Confidențialitatea privind senzorii"</string>
     <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Pictograma aplicației"</string>
     <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Imaginea de branding a aplicației"</string>
-    <string name="view_and_control_notification_title" msgid="4300765399209912240">"Verificați setările pentru acces"</string>
-    <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> poate să vadă și să vă controleze ecranul. Atingeți pentru a examina."</string>
+    <string name="view_and_control_notification_title" msgid="4300765399209912240">"Verifică setările pentru acces"</string>
+    <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> poate să vadă și să controleze ecranul. Atinge pentru a verifica."</string>
     <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> a fost tradus."</string>
     <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Mesaj tradus din <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> în <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
     <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Activitate de fundal"</string>
@@ -2289,7 +2290,7 @@
     <string name="notification_title_long_running_fgs" msgid="8170284286477131587">"O aplicație este încă activă"</string>
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> rulează în fundal. Atinge pentru a gestiona utilizarea bateriei."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> poate afecta autonomia bateriei. Atinge pentru a examina aplicațiile active."</string>
-    <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Verificați aplicațiile active"</string>
+    <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Verifică aplicațiile active"</string>
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nu se poate accesa camera foto a telefonului de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nu se poate accesa camera foto a tabletei de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Nu se poate accesa în timpul streamingului. Încearcă pe telefon."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 71668fa..607da13 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -263,7 +263,7 @@
     <string name="global_action_settings" msgid="4671878836947494217">"Настройки"</string>
     <string name="global_action_assist" msgid="2517047220311505805">"Помощник"</string>
     <string name="global_action_voice_assist" msgid="6655788068555086695">"Аудиоподсказки"</string>
-    <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка"</string>
+    <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string>
     <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
     <string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string>
     <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуальная клавиатура"</string>
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Разрешить приложению \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" доступ ко всем журналам устройства?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешить разовый доступ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Запретить"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больше не показывать"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Приложение \"<xliff:g id="APP_0">%1$s</xliff:g>\" запрашивает разрешение на показ фрагментов приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Изменить"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 90b71de..22f1311 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> හට සියලු උපාංග ලොග ප්‍රවේශ වීමට ඉඩ දෙන්නද?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"එක් වරක් ප්‍රවේශය ඉඩ දෙන්න"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ඉඩ නොදෙන්න"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"නැවත නොපෙන්වන්න"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට අවශ්‍යයි"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"සංස්කරණය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 121e410..fbe5fd2 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -2052,7 +2052,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Chcete povoliť aplikácii <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> prístup k všetkým denníkom zariadenia?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povoliť jednorazový prístup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovoliť"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení.\n\nViac sa dozviete na g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Už nezobrazovať"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Upraviť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 8fc73ce..105e783 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ali aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> dovolite dostop do vseh dnevnikov naprave?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dovoli enkratni dostop"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dovoli"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikaži več"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 77fe612..3a5a5ab 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Të lejohet që <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> të ketë qasje te të gjitha evidencat e pajisjes?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Lejo qasjen vetëm për një herë"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Mos lejo"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Mos e shfaq më"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> dëshiron të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifiko"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index c38c771..ff87cdb 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -2051,7 +2051,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Желите да дозволите апликацији <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да приступа свим евиденцијама уређаја?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи једнократан приступ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволи"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају.\n\nСазнајте више на g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не приказуј поново"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Апликација <xliff:g id="APP_0">%1$s</xliff:g> жели да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index e65231d..34f0f4f 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vill du tillåta att <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> får åtkomst till alla enhetsloggar?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillåt engångsåtkomst"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillåt inte"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Visa inte igen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill kunna visa bitar av <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redigera"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 8ec29b3..9c8aecd 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ungependa kuruhusu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ifikie kumbukumbu zote za kifaa?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Ruhusu ufikiaji wa mara moja"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Usiruhusu"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Kumbukumbu za kifaa zinarekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Kumbukumbu za kifaa zinarekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Usionyeshe tena"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> inataka kuonyesha vipengee <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Badilisha"</string>
@@ -2289,7 +2291,7 @@
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> inatumika chinichini. Gusa ili udhibiti matumizi ya betri."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> inaweza kuathiri muda wa matumizi ya betri. Gusa ili ukague programu zinazotumika."</string>
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Angalia programu zinazotumika"</string>
-    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Haiwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
+    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Huwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Haiwezi kufikia kamera ya kompyuta kibao kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Huwezi kufikia maudhui haya unapotiririsha. Badala yake jaribu kwenye simu yako."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Chaguomsingi la mfumo"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index ea5227c..699495f 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"சாதனப் பதிவுகள் அனைத்தையும் <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> அணுக அனுமதிக்கவா?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ஒருமுறை அணுகலை அனுமதி"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"அனுமதிக்க வேண்டாம்"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸ் இந்தப் பதிவுகளைப் பயன்படுத்தலாம்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள் சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக நீங்கள் நம்பும் ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸ் இந்தப் பதிவுகளைப் பயன்படுத்தலாம்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள் சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக நீங்கள் நம்பும் ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸால் இந்தப் பதிவுகளைப் பயன்படுத்த முடியும்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள், சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக உங்களுக்கு நம்பகமான ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை நீங்கள் அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்.\n\n மேலும் அறிந்துகொள்ள g.co/android/devicelogs இணைப்பிற்குச் செல்லுங்கள்."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"மீண்டும் காட்டாதே"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> அனுமதி கேட்கிறது"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"திருத்து"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 66dcac6..a771e60 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‌ను అనుమతించాలా?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"వన్-టైమ్ యాక్సెస్‌ను అనుమతించండి"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"అనుమతించవద్దు"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"మళ్లీ చూపవద్దు"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> స్లైస్‌లను చూపించాలనుకుంటోంది"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ఎడిట్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 0d1ea17..44a810b 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"อนุญาตให้ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> เข้าถึงบันทึกทั้งหมดของอุปกรณ์ใช่ไหม"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"อนุญาตสิทธิ์เข้าถึงแบบครั้งเดียว"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ไม่อนุญาต"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ \n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ของคุณได้"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ \n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ของคุณได้"</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์\n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ได้\n\nดูข้อมูลเพิ่มเติมที่ g.co/android/devicelogs"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ไม่ต้องแสดงอีก"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ต้องการแสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"แก้ไข"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 84f6112..156ba69 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -2050,7 +2050,8 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Payagan ang <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na i-access ang lahat ng log ng device?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Payagan ang isang beses na pag-access"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Huwag payagan"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo."</string>
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo.\n\nMatuto pa sa g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Huwag ipakita ulit"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Gustong ipakita ng <xliff:g id="APP_0">%1$s</xliff:g> ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"I-edit"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index e0c1585..9e782f2 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> uygulamasının tüm cihaz günlüklerine erişmesine izin verilsin mi?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tek seferlik erişim izni ver"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İzin verme"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Bir daha gösterme"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> uygulaması, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermek istiyor"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Düzenle"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index c76d6a3..e63e2ac 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1080,7 +1080,7 @@
     <string name="searchview_description_search" msgid="1045552007537359343">"Пошук"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"Пошуковий запит"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"Очистити запит"</string>
-    <string name="searchview_description_submit" msgid="6771060386117334686">"Наіслати запит"</string>
+    <string name="searchview_description_submit" msgid="6771060386117334686">"Надіслати запит"</string>
     <string name="searchview_description_voice" msgid="42360159504884679">"Голосовий пошук"</string>
     <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Увімкнути дослідження дотиком?"</string>
     <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> хоче ввімкнути функцію дослідження дотиком. Увімкнувши функцію дослідження дотиком, можна чути або бачити опис елемента, розташованого під вашим пальцем, або виконувати жести для взаємодії з планшетним ПК."</string>
@@ -2052,7 +2052,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Надати додатку <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> доступ до всіх журналів пристрою?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Надати доступ лише цього разу"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволяти"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Більше не показувати"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> хоче показати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редагувати"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index c589440..21a0818 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> کو آلے کے تمام لاگز تک رسائی کی اجازت دیں؟"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"یک وقتی رسائی کی اجازت دیں"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازت نہ دیں"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنے بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنے بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوبارہ نہ دکھائیں"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانا چاہتی ہے"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ترمیم کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a0bcc98..b23796c 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ilovasining qurilmadagi barcha jurnallarga kirishiga ruxsat berilsinmi?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Bir matalik foydalanishga ruxsat berish"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Rad etish"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Boshqa chiqmasin"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasi <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatish uchun ruxsat so‘ramoqda"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Tahrirlash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index c94730a..2bb8a05 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1231,7 +1231,7 @@
     <string name="unsupported_display_size_show" msgid="980129850974919375">"Luôn hiển thị"</string>
     <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> được xây dựng cho phiên bản không tương thích của hệ điều hành Android và có thể hoạt động không như mong đợi. Bạn có thể sử dụng phiên bản cập nhật của ứng dụng."</string>
     <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Luôn hiển thị"</string>
-    <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Kiểm tra bản cập nhật"</string>
+    <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Kiểm tra để tìm bản cập nhật"</string>
     <string name="smv_application" msgid="3775183542777792638">"Ứng dụng <xliff:g id="APPLICATION">%1$s</xliff:g> (quá trình <xliff:g id="PROCESS">%2$s</xliff:g>) đã vi phạm chính sách StrictMode tự thi hành của mình."</string>
     <string name="smv_process" msgid="1398801497130695446">"Quá trình <xliff:g id="PROCESS">%1$s</xliff:g> đã vi phạm chính sách StrictMode tự thi hành của mình."</string>
     <string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Điện thoại đang cập nhật…"</string>
@@ -1959,7 +1959,7 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"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_for_settings_dialog" product="default" msgid="6264287556598916295">"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="deprecated_target_sdk_message" msgid="5203207875657579953">"Ứng dụng này được xây dựng cho một phiên bản Android cũ hơn và có thể hoạt động không bình thường. Hãy thử kiểm tra các bản cập nhật hoặc liên hệ với nhà phát triển."</string>
-    <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra bản cập nhật"</string>
+    <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra để tìm bản cập nhật"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Bạn có tin nhắn mới"</string>
     <string name="new_sms_notification_content" msgid="3197949934153460639">"Mở ứng dụng SMS để xem"</string>
     <string name="profile_encrypted_title" msgid="9001208667521266472">"Một số chức năng có thể bị hạn chế"</string>
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Cho phép <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> truy cập vào tất cả các nhật ký thiết bị?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Cho phép truy cập một lần"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Không cho phép"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào toàn bộ nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào toàn bộ nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Không hiện lại"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> muốn hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Chỉnh sửa"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index b81633e4..e558031 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"允许“<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>”访问所有设备日志吗?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允许访问一次"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允许"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不再显示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"“<xliff:g id="APP_0">%1$s</xliff:g>”想要显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"编辑"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index d7f33a5..f4f12c1 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -648,9 +648,9 @@
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"請重新註冊面孔。"</string>
     <string name="face_acquired_too_different" msgid="2520389515612972889">"無法辨識面孔,請再試一次。"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"請稍為轉換頭部的位置"</string>
-    <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"盡可能直視手機"</string>
+    <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"請正面望向手機"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"請正面望向手機"</string>
-    <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"盡可能直視手機"</string>
+    <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"請正面望向手機"</string>
     <string name="face_acquired_obscured" msgid="4917643294953326639">"移開遮住面孔的任何物件。"</string>
     <string name="face_acquired_sensor_dirty" msgid="8968391891086721678">"請清理螢幕頂部,包括黑色列"</string>
     <!-- no translation found for face_acquired_dark_glasses_detected (5643703296620631986) -->
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許存取一次"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"裝置記錄會記下裝置上的活動。應用程式可透過這些記錄找出並修正問題。\n\n部分記錄可能包含敏感資料,因此請只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄,且裝置製造商可能仍可存取裝置上的部分記錄或資料。"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"裝置記錄會記下裝置上的活動。應用程式可透過這些記錄找出並修正問題。\n\n部分記錄可能包含敏感資料,因此請只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄,且裝置製造商可能仍可存取裝置上的部分記錄或資料。"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 44aeb10..f4bfdda 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許一次性存取"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想要顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index d4d1f2a..8f50ab0 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2050,7 +2050,9 @@
     <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vumela i-<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ukuba ifinyelele wonke amalogu edivayisi?"</string>
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Vumela ukufinyelela kwesikhathi esisodwa"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ungavumeli"</string>
-    <string name="log_access_confirmation_body" msgid="1806692062668620735">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
+    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
+    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
+    <skip />
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ungabonisi futhi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"I-<xliff:g id="APP_0">%1$s</xliff:g> ifuna ukubonisa izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Hlela"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1d2ce7e..47faf2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1662,13 +1662,18 @@
          darkening hysteresis constraint value is the n-th element of
          config_screenDarkeningThresholds.
 
+         Historically, it has been assumed that this will be an integer array with values in the
+         range of [0, 255]. However, it is now assumed to be a float array with values in the
+         range of [0, 1]. To accommodate both the possibilities, we internally check the scale on
+         which the thresholds are defined, and calibrate it accordingly.
+
          The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
          condition                       calculated index
          value < level[0]                0
          level[n] <= value < level[n+1]  n+1
          level[MAX] <= value             MAX+1 -->
-    <integer-array name="config_screenThresholdLevels">
-    </integer-array>
+    <array name="config_screenThresholdLevels">
+    </array>
 
     <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
          percent. The length of this array is assumed to be one greater than
@@ -3017,6 +3022,12 @@
          when alpha identifier is not provided by the UICC -->
     <bool name="config_stkNoAlphaUsrCnf">true</bool>
 
+    <!-- Flag indicating whether the current device allows stk sms send service via framework.
+         If true,this means that the device supports sending of stk triggered sms via the telephony.
+         This can be overridden to false for devices which can't send stk sms message via
+         framework, but would be sent via modem. -->
+    <bool name="config_stk_sms_send_support">false</bool>
+
     <!-- Threshold (in ms) under which a screen off / screen on will be considered a reset of the
          immersive mode confirmation prompt.-->
     <integer name="config_immersive_mode_confirmation_panic">5000</integer>
@@ -3320,7 +3331,7 @@
     <!--From SmsMessage-->
     <!--Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
         string that's stored in 8-bit unpacked format) characters.-->
-    <bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
+    <bool translatable="false" name="config_sms_decode_gsm_8bit_data">true</bool>
 
     <!-- Configures encoding type to parse the User Data of an SMS for reserved TP-DCS value.
          Refer to SmsConstants.java
@@ -3656,10 +3667,6 @@
          is interactive. -->
     <bool name="config_volumeHushGestureEnabled">true</bool>
 
-    <!-- Name of the component to handle network policy notifications. If present,
-         disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
-    <string translatable="false" name="config_networkPolicyNotificationComponent"></string>
-
     <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
          automatically try to pair with it when the device exits tablet mode. -->
     <string translatable="false" name="config_packagedKeyboardName"></string>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index ea2b988..a1d73ff 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -113,4 +113,8 @@
          new network. -->
     <bool name="config_enhanced_iwlan_handover_check">true</bool>
     <java-symbol type="bool" name="config_enhanced_iwlan_handover_check" />
+
+    <!-- Whether using the new SubscriptionManagerService or the old SubscriptionController -->
+    <bool name="config_using_subscription_manager_service">false</bool>
+    <java-symbol type="bool" name="config_using_subscription_manager_service" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9214f43..5f99113 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5512,7 +5512,7 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
 
     <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
-    <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
+    <string name="deprecated_target_sdk_message">This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update, or contact the app\'s developer.</string>
     <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
     <string name="deprecated_target_sdk_app_store">Check for update</string>
 
@@ -5760,10 +5760,21 @@
     <string name="log_access_confirmation_deny">Don\u2019t allow</string>
 
     <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+    <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
         \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
     </string>
 
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
+    </string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" product="default" translatable="false">&lt;a href="https://support.google.com/android?p=system_logs#topic=7313011"&gt;Learn more&lt;/a&gt;</string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string>
+
     <!-- Privacy notice do not show [CHAR LIMIT=20] -->
     <string name="log_access_do_not_show_again">Don\u2019t show again</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 95a81e8..b42db13 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2444,6 +2444,7 @@
   <java-symbol type="drawable" name="ic_volume" />
   <java-symbol type="drawable" name="stat_notify_sim_toolkit" />
   <java-symbol type="bool" name="config_stkNoAlphaUsrCnf" />
+  <java-symbol type="bool" name="config_stk_sms_send_support" />
 
   <!-- From maps library -->
   <java-symbol type="array" name="maps_starting_lat_lng" />
@@ -3934,8 +3935,10 @@
   <java-symbol type="string" name="log_access_confirmation_deny" />
   <java-symbol type="string" name="log_access_confirmation_title" />
   <java-symbol type="string" name="log_access_confirmation_body" />
+  <java-symbol type="string" name="log_access_confirmation_learn_more" />
   <java-symbol type="layout" name="log_access_user_consent_dialog_permission" />
   <java-symbol type="id" name="log_access_dialog_title" />
+  <java-symbol type="id" name="log_access_dialog_body" />
   <java-symbol type="id" name="log_access_dialog_allow_button" />
   <java-symbol type="id" name="log_access_dialog_deny_button" />
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
new file mode 100644
index 0000000..e2556d67
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.util.ArrayMap;
+
+final class AidlTestUtils {
+
+    private AidlTestUtils() {
+        throw new UnsupportedOperationException("AidlTestUtils class is noninstantiable");
+    }
+
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+        return new RadioManager.ProgramInfo(selector,
+                selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+                /* infoFlags= */ 0, signalQuality,
+                new RadioMetadata.Builder().build(), new ArrayMap<>());
+    }
+
+    static RadioManager.ProgramInfo makeProgramInfo(int programType,
+            ProgramSelector.Identifier identifier, int signalQuality) {
+        ProgramSelector selector = makeProgramSelector(programType, identifier);
+        return makeProgramInfo(selector, signalQuality);
+    }
+
+    static ProgramSelector makeFMSelector(long freq) {
+        return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+                        freq));
+    }
+
+    static ProgramSelector makeProgramSelector(int programType,
+            ProgramSelector.Identifier identifier) {
+        return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+                /* vendorIds= */ null);
+    }
+
+    static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
+        // Note that because ConversionUtils does not by design provide functions for all
+        // conversions, this function only copies fields that are set by makeProgramInfo().
+        ProgramInfo hwInfo = new ProgramInfo();
+        hwInfo.selector = ConversionUtils.programSelectorToHalProgramSelector(info.getSelector());
+        hwInfo.logicallyTunedTo =
+                ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo());
+        hwInfo.physicallyTunedTo =
+                ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo());
+        hwInfo.signalQuality = info.getSignalStrength();
+        hwInfo.relatedContent = new ProgramIdentifier[]{};
+        hwInfo.metadata = new Metadata[]{};
+        return hwInfo;
+    }
+
+    static ProgramInfo makeHalProgramSelector(
+            android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
+        ProgramInfo hwInfo = new ProgramInfo();
+        hwInfo.selector = hwSel;
+        hwInfo.logicallyTunedTo = hwSel.primaryId;
+        hwInfo.physicallyTunedTo = hwSel.primaryId;
+        hwInfo.signalQuality = hwSignalQuality;
+        hwInfo.relatedContent = new ProgramIdentifier[]{};
+        hwInfo.metadata = new Metadata[]{};
+        return hwInfo;
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
new file mode 100644
index 0000000..518c3be
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+/**
+ * Tests for AIDL HAL RadioModule.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerSessionTest {
+    private static final VerificationWithTimeout CALLBACK_TIMEOUT =
+            timeout(/* millis= */ 200);
+
+    private final int mSignalQuality = 1;
+    private final long mAmfmFrequencySpacing = 500;
+    private final long[] mAmfmFrequencyList = {97500, 98100, 99100};
+
+    // Mocks
+    @Mock private IBroadcastRadio mBroadcastRadioMock;
+    private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+
+    private final Object mLock = new Object();
+    // RadioModule under test
+    private RadioModule mRadioModule;
+
+    // Objects created by mRadioModule
+    private ITunerCallback mHalTunerCallback;
+    private ProgramInfo mHalCurrentInfo;
+    private TunerSession[] mTunerSessions;
+
+    @Before
+    public void setup() throws RemoteException {
+        mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
+                /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+                /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+                /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+                /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
+                new int[] {}, new int[] {},
+                /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+
+        doAnswer(invocation -> {
+            mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
+            return null;
+        }).when(mBroadcastRadioMock).setTunerCallback(any());
+        mRadioModule.setInternalHalCallback();
+
+        doAnswer(invocation -> {
+            mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+                    (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0],
+                    mSignalQuality);
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return null;
+        }).when(mBroadcastRadioMock).tune(any());
+
+        doAnswer(invocation -> {
+            if ((boolean) invocation.getArguments()[0]) {
+                mHalCurrentInfo.selector.primaryId.value += mAmfmFrequencySpacing;
+            } else {
+                mHalCurrentInfo.selector.primaryId.value -= mAmfmFrequencySpacing;
+            }
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return null;
+        }).when(mBroadcastRadioMock).step(anyBoolean());
+
+        doAnswer(invocation -> {
+            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+                    mHalCurrentInfo.selector.primaryId.value,
+                    !(boolean) invocation.getArguments()[0]);
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return null;
+        }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
+    }
+
+    @Test
+    public void openSession_withMultipleSessions() throws RemoteException {
+        int numSessions = 3;
+
+        openAidlClients(numSessions);
+
+        for (int index = 0; index < numSessions; index++) {
+            assertWithMessage("Session of index %s close state", index)
+                    .that(mTunerSessions[index].isClosed()).isFalse();
+        }
+    }
+
+    @Test
+    public void close_withOneSession() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].close();
+
+        assertWithMessage("Close state of broadcast radio service session")
+                .that(mTunerSessions[0].isClosed()).isTrue();
+    }
+
+    @Test
+    public void close_withOnlyOneSession_withMultipleSessions() throws RemoteException {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        int closeIdx = 0;
+
+        mTunerSessions[closeIdx].close();
+
+        for (int index = 0; index < numSessions; index++) {
+            if (index == closeIdx) {
+                assertWithMessage(
+                        "Close state of broadcast radio service session of index %s", index)
+                        .that(mTunerSessions[index].isClosed()).isTrue();
+            } else {
+                assertWithMessage(
+                        "Close state of broadcast radio service session of index %s", index)
+                        .that(mTunerSessions[index].isClosed()).isFalse();
+            }
+        }
+    }
+
+    @Test
+    public void close_withOneSession_withError() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        int errorCode = RadioTuner.ERROR_SERVER_DIED;
+
+        mTunerSessions[0].close(errorCode);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
+        assertWithMessage("Close state of broadcast radio service session")
+                .that(mTunerSessions[0].isClosed()).isTrue();
+    }
+
+    @Test
+    public void closeSessions_withMultipleSessions_withError() throws RemoteException {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+
+        int errorCode = RadioTuner.ERROR_SERVER_DIED;
+        mRadioModule.closeSessions(errorCode);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
+            assertWithMessage("Close state of broadcast radio service session of index %s", index)
+                    .that(mTunerSessions[index].isClosed()).isTrue();
+        }
+    }
+
+    @Test
+    public void tune_withOneSession() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
+    public void tune_withMultipleSessions() throws RemoteException {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+
+        mTunerSessions[0].tune(initialSel);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onCurrentProgramInfoChanged(tuneInfo);
+        }
+    }
+
+    @Test
+    public void step_withDirectionUp() throws RemoteException {
+        long initFreq = mAmfmFrequencyList[1];
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+        RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFMSelector(initFreq + mAmfmFrequencySpacing),
+                mSignalQuality);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+        mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(stepUpInfo);
+    }
+
+    @Test
+    public void step_withDirectionDown() throws RemoteException {
+        long initFreq = mAmfmFrequencyList[1];
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+        RadioManager.ProgramInfo stepDownInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFMSelector(initFreq - mAmfmFrequencySpacing),
+                mSignalQuality);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+        mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(stepDownInfo);
+    }
+
+    @Test
+    public void scan_withDirectionUp() throws RemoteException {
+        long initFreq = mAmfmFrequencyList[2];
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+                mSignalQuality);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(scanUpInfo);
+    }
+
+    @Test
+    public void scan_withDirectionDown() throws RemoteException {
+        long initFreq = mAmfmFrequencyList[2];
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+                mSignalQuality);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(scanUpInfo);
+    }
+
+    @Test
+    public void cancel() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+        mTunerSessions[0].tune(initialSel);
+
+        mTunerSessions[0].cancel();
+
+        verify(mBroadcastRadioMock).cancel();
+    }
+
+    @Test
+    public void getImage_withInvalidId_throwsIllegalArgumentException() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        int imageId = IBroadcastRadio.INVALID_IMAGE;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mTunerSessions[0].getImage(imageId);
+        });
+
+        assertWithMessage("Exception for getting image with invalid ID")
+                .that(thrown).hasMessageThat().contains("Image ID is missing");
+    }
+
+    @Test
+    public void getImage_withValidId() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        int imageId = 1;
+
+        Bitmap imageTest = mTunerSessions[0].getImage(imageId);
+
+        assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+    }
+
+    @Test
+    public void startBackgroundScan() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].startBackgroundScan();
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
+    }
+
+    @Test
+    public void stopProgramListUpdates() throws RemoteException {
+        openAidlClients(/* numClients= */ 1);
+        ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+                /* includeCategories= */ true, /* excludeModifications= */ false);
+        mTunerSessions[0].startProgramListUpdates(aidlFilter);
+
+        mTunerSessions[0].stopProgramListUpdates();
+
+        verify(mBroadcastRadioMock).stopProgramListUpdates();
+    }
+
+    private void openAidlClients(int numClients) throws RemoteException {
+        mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
+        mTunerSessions = new TunerSession[numClients];
+        for (int index = 0; index < numClients; index++) {
+            mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
+            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+        }
+    }
+
+    private long getSeekFrequency(long currentFrequency, boolean seekDown) {
+        long seekFrequency;
+        if (seekDown) {
+            seekFrequency = mAmfmFrequencyList[mAmfmFrequencyList.length - 1];
+            for (int i = mAmfmFrequencyList.length - 1; i >= 0; i--) {
+                if (mAmfmFrequencyList[i] < currentFrequency) {
+                    seekFrequency = mAmfmFrequencyList[i];
+                    break;
+                }
+            }
+        } else {
+            seekFrequency = mAmfmFrequencyList[0];
+            for (int index = 0; index < mAmfmFrequencyList.length; index++) {
+                if (mAmfmFrequencyList[index] > currentFrequency) {
+                    seekFrequency = mAmfmFrequencyList[index];
+                    break;
+                }
+            }
+        }
+        return seekFrequency;
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 90b3305..92149eb 100644
--- a/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -40,14 +40,14 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUnixEpochTime() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321");
+                "--elapsed_realtime 54321");
         ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test
     public void testParseCommandLineArg_validSuggestion() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
         ExternalTimeSuggestion expectedSuggestion = new ExternalTimeSuggestion(54321L, 12345L);
         ExternalTimeSuggestion actualSuggestion =
                 ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
@@ -57,7 +57,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_unknownArgument() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
         ExternalTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 }
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index 9d7dde2..c9b96c6 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -48,10 +48,10 @@
     public void testEquals() {
         TimeCapabilities.Builder builder1 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeCapability(CAPABILITY_POSSESSED);
         TimeCapabilities.Builder builder2 = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeCapability(CAPABILITY_POSSESSED);
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
@@ -72,14 +72,14 @@
             assertEquals(one, two);
         }
 
-        builder2.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
+        builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
             assertNotEquals(one, two);
         }
 
-        builder1.setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED);
+        builder1.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
@@ -91,12 +91,12 @@
     public void userHandle_notIgnoredInEquals() {
         TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1))
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+                .setSetManualTimeCapability(CAPABILITY_POSSESSED)
                 .build();
 
         TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2))
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+                .setSetManualTimeCapability(CAPABILITY_POSSESSED)
                 .build();
 
         assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities);
@@ -106,12 +106,12 @@
     public void testBuilder() {
         TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE)
-                .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
+                .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED)
                 .build();
 
         assertThat(capabilities.getConfigureAutoDetectionEnabledCapability())
                 .isEqualTo(CAPABILITY_NOT_APPLICABLE);
-        assertThat(capabilities.getSuggestManualTimeCapability())
+        assertThat(capabilities.getSetManualTimeCapability())
                 .isEqualTo(CAPABILITY_NOT_SUPPORTED);
 
         try {
@@ -133,7 +133,7 @@
 
         try {
             new TimeCapabilities.Builder(TEST_USER_HANDLE)
-                    .setSuggestManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
+                    .setSetManualTimeCapability(CAPABILITY_NOT_APPLICABLE)
                     .build();
             fail("Should throw IllegalStateException");
         } catch (IllegalStateException ignored) {
@@ -145,11 +145,11 @@
     public void testParcelable() {
         TimeCapabilities.Builder builder = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED)
-                .setSuggestManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
+                .setSetManualTimeCapability(CAPABILITY_NOT_SUPPORTED);
 
         assertRoundTripParcelable(builder.build());
 
-        builder.setSuggestManualTimeCapability(CAPABILITY_POSSESSED);
+        builder.setSetManualTimeCapability(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
 
         builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED);
@@ -164,7 +164,7 @@
                         .build();
         TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+                .setSetManualTimeCapability(CAPABILITY_POSSESSED)
                 .build();
 
         TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -185,7 +185,7 @@
                         .build();
         TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+                .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         TimeConfiguration configChange = new TimeConfiguration.Builder()
@@ -199,7 +199,7 @@
     public void copyBuilder_copiesAllFields() {
         TimeCapabilities capabilities = new TimeCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+                .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         {
@@ -210,7 +210,7 @@
             TimeCapabilities expectedCapabilities =
                     new TimeCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                            .setSuggestManualTimeCapability(CAPABILITY_NOT_ALLOWED)
+                            .setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -219,13 +219,13 @@
         {
             TimeCapabilities updatedCapabilities =
                     new TimeCapabilities.Builder(capabilities)
-                            .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+                            .setSetManualTimeCapability(CAPABILITY_POSSESSED)
                             .build();
 
             TimeCapabilities expectedCapabilities =
                     new TimeCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                            .setSuggestManualTimeCapability(CAPABILITY_POSSESSED)
+                            .setSetManualTimeCapability(CAPABILITY_POSSESSED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
new file mode 100644
index 0000000..bce0909
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link TimeState}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeStateTest {
+
+    @Test
+    public void testEqualsAndHashcode() {
+        UnixEpochTime time1 = new UnixEpochTime(1, 1);
+        TimeState time1False_1 = new TimeState(time1, false);
+        assertEqualsAndHashCode(time1False_1, time1False_1);
+
+        TimeState time1False_2 = new TimeState(time1, false);
+        assertEqualsAndHashCode(time1False_1, time1False_2);
+
+        TimeState time1True = new TimeState(time1, true);
+        assertNotEquals(time1False_1, time1True);
+
+        UnixEpochTime time2 = new UnixEpochTime(2, 2);
+        TimeState time2False = new TimeState(time2, false);
+        assertNotEquals(time1False_1, time2False);
+    }
+
+    private static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+
+    @Test
+    public void testParceling() {
+        UnixEpochTime time = new UnixEpochTime(1, 2);
+        assertRoundTripParcelable(new TimeState(time, true));
+        assertRoundTripParcelable(new TimeState(time, false));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noElapsedRealtime() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--unix_epoch_time 12345 --user_should_confirm_time true");
+        TimeState.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noUnixEpochTime() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321 --user_should_confirm_time true");
+        TimeState.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noUserShouldConfirmTime() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--unix_epoch_time 12345 --elapsed_realtime 54321");
+        TimeState.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test
+    public void testParseCommandLineArg_validSuggestion() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true");
+        TimeState expectedValue = new TimeState(new UnixEpochTime(54321L, 12345L), true);
+        TimeState actualValue = TimeState.parseCommandLineArgs(testShellCommand);
+        assertEquals(expectedValue, actualValue);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_unknownArgument() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --user_should_confirm_time true"
+                        + " --bad_arg 0");
+        TimeState.parseCommandLineArgs(testShellCommand);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 0082728..3f7da8a 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -45,11 +45,11 @@
         TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         {
             TimeZoneCapabilities one = builder1.build();
             TimeZoneCapabilities two = builder2.build();
@@ -84,14 +84,14 @@
             assertEquals(one, two);
         }
 
-        builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        builder2.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
             TimeZoneCapabilities two = builder2.build();
             assertNotEquals(one, two);
         }
 
-        builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        builder1.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
             TimeZoneCapabilities two = builder2.build();
@@ -104,7 +104,7 @@
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
 
         builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -113,7 +113,7 @@
         builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
 
-        builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+        builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
     }
 
@@ -127,7 +127,7 @@
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
                 .build();
 
         TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -150,7 +150,7 @@
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -165,7 +165,7 @@
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         {
@@ -177,7 +177,7 @@
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                            .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -193,7 +193,7 @@
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                            .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -202,14 +202,14 @@
         {
             TimeZoneCapabilities updatedCapabilities =
                     new TimeZoneCapabilities.Builder(capabilities)
-                            .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
                             .build();
 
             TimeZoneCapabilities expectedCapabilities =
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
-                            .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
new file mode 100644
index 0000000..35a9dbc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link TimeZoneState}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TimeZoneStateTest {
+
+    @Test
+    public void testEqualsAndHashcode() {
+        String zone1 = "Europe/London";
+        TimeZoneState zone1False_1 = new TimeZoneState(zone1, false);
+        assertEqualsAndHashCode(zone1False_1, zone1False_1);
+
+        TimeZoneState zone1False_2 = new TimeZoneState(zone1, false);
+        assertEqualsAndHashCode(zone1False_1, zone1False_2);
+
+        TimeZoneState zone1True = new TimeZoneState(zone1, true);
+        assertNotEquals(zone1False_1, zone1True);
+
+        String zone2 = "Europe/Parise";
+        TimeZoneState zone2False = new TimeZoneState(zone2, false);
+        assertNotEquals(zone1False_1, zone2False);
+    }
+
+    private static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+
+    @Test
+    public void testParceling() {
+        assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
+        assertRoundTripParcelable(new TimeZoneState("Europe/London", false));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noZoneId() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--user_should_confirm_id true");
+        TimeZoneState.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noUserShouldConfirmId() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--zone_id Europe/London");
+        TimeZoneState.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test
+    public void testParseCommandLineArg_validSuggestion() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--zone_id Europe/London --user_should_confirm_id true");
+        TimeZoneState expectedValue = new TimeZoneState("Europe/London", true);
+        TimeZoneState actualValue = TimeZoneState.parseCommandLineArgs(testShellCommand);
+        assertEquals(expectedValue, actualValue);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_unknownArgument() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--zone_id Europe/London --user_should_confirm_id true --bad_arg 0");
+        TimeZoneState.parseCommandLineArgs(testShellCommand);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
new file mode 100644
index 0000000..3ab01f3
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.ShellCommand;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for non-SDK methods on {@link UnixEpochTime}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class UnixEpochTimeTest {
+
+    @Test
+    public void testEqualsAndHashcode() {
+        UnixEpochTime one1000one = new UnixEpochTime(1000, 1);
+        assertEqualsAndHashCode(one1000one, one1000one);
+
+        UnixEpochTime one1000two = new UnixEpochTime(1000, 1);
+        assertEqualsAndHashCode(one1000one, one1000two);
+
+        UnixEpochTime two1000 = new UnixEpochTime(1000, 2);
+        assertNotEquals(one1000one, two1000);
+
+        UnixEpochTime one2000 = new UnixEpochTime(2000, 1);
+        assertNotEquals(one1000one, one2000);
+    }
+
+    private static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+
+    @Test
+    public void testParceling() {
+        assertRoundTripParcelable(new UnixEpochTime(1000, 1));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noElapsedRealtime() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--unix_epoch_time 12345");
+        UnixEpochTime.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noUnixEpochTime() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321");
+        UnixEpochTime.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test
+    public void testParseCommandLineArg_validSuggestion() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
+        UnixEpochTime expectedValue = new UnixEpochTime(54321L, 12345L);
+        UnixEpochTime actualValue = UnixEpochTime.parseCommandLineArgs(testShellCommand);
+        assertEquals(expectedValue, actualValue);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_unknownArgument() {
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
+        UnixEpochTime.parseCommandLineArgs(testShellCommand);
+    }
+
+    @Test
+    public void testAt() {
+        long timeMillis = 1000L;
+        int elapsedRealtimeMillis = 100;
+        UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis);
+        // Reference time is after the timestamp.
+        UnixEpochTime at125 = unixEpochTime.at(125);
+        assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis());
+        assertEquals(125, at125.getElapsedRealtimeMillis());
+
+        // Reference time is before the timestamp.
+        UnixEpochTime at75 = unixEpochTime.at(75);
+        assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis());
+        assertEquals(75, at75.getElapsedRealtimeMillis());
+    }
+
+    @Test
+    public void testElapsedRealtimeDifference() {
+        UnixEpochTime value1 = new UnixEpochTime(1000, 123L);
+        assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1));
+
+        UnixEpochTime value2 = new UnixEpochTime(1, 321L);
+        assertEquals(999, UnixEpochTime.elapsedRealtimeDifference(value1, value2));
+        assertEquals(-999, UnixEpochTime.elapsedRealtimeDifference(value2, value1));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 94218cd..0c7c8c1 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -23,15 +23,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.time.UnixEpochTime;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import org.junit.Test;
 
 public class ManualTimeSuggestionTest {
 
-    private static final TimestampedValue<Long> ARBITRARY_TIME =
-            new TimestampedValue<>(1111L, 2222L);
+    private static final UnixEpochTime ARBITRARY_TIME = new UnixEpochTime(1111L, 2222L);
 
     @Test
     public void testEquals() {
@@ -42,9 +41,9 @@
         assertEquals(one, two);
         assertEquals(two, one);
 
-        TimestampedValue<Long> differentTime = new TimestampedValue<>(
-                ARBITRARY_TIME.getReferenceTimeMillis() + 1,
-                ARBITRARY_TIME.getValue());
+        UnixEpochTime differentTime = new UnixEpochTime(
+                ARBITRARY_TIME.getElapsedRealtimeMillis() + 1,
+                ARBITRARY_TIME.getUnixEpochTimeMillis());
         ManualTimeSuggestion three = new ManualTimeSuggestion(differentTime);
         assertNotEquals(one, three);
         assertNotEquals(three, one);
@@ -76,15 +75,15 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUnixEpochTime() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321");
+                "--elapsed_realtime 54321");
         ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test
     public void testParseCommandLineArg_validSuggestion() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345");
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
+        UnixEpochTime timeSignal = new UnixEpochTime(54321L, 12345L);
         ManualTimeSuggestion expectedSuggestion = new ManualTimeSuggestion(timeSignal);
         ManualTimeSuggestion actualSuggestion =
                 ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
@@ -94,7 +93,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_unknownArgument() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
         ManualTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 }
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index bb995a8..26cb902 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -23,8 +23,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.time.UnixEpochTime;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import org.junit.Test;
 
@@ -47,13 +47,13 @@
             assertEquals(two, one);
         }
 
-        builder1.setUnixEpochTime(new TimestampedValue<>(1111L, 2222L));
+        builder1.setUnixEpochTime(new UnixEpochTime(1111L, 2222L));
         {
             TelephonyTimeSuggestion one = builder1.build();
             assertEquals(one, one);
         }
 
-        builder2.setUnixEpochTime(new TimestampedValue<>(1111L, 2222L));
+        builder2.setUnixEpochTime(new UnixEpochTime(1111L, 2222L));
         {
             TelephonyTimeSuggestion one = builder1.build();
             TelephonyTimeSuggestion two = builder2.build();
@@ -63,7 +63,7 @@
 
         TelephonyTimeSuggestion.Builder builder3 =
                 new TelephonyTimeSuggestion.Builder(SLOT_INDEX + 1);
-        builder3.setUnixEpochTime(new TimestampedValue<>(1111L, 2222L));
+        builder3.setUnixEpochTime(new UnixEpochTime(1111L, 2222L));
         {
             TelephonyTimeSuggestion one = builder1.build();
             TelephonyTimeSuggestion three = builder3.build();
@@ -86,7 +86,7 @@
         TelephonyTimeSuggestion.Builder builder = new TelephonyTimeSuggestion.Builder(SLOT_INDEX);
         assertRoundTripParcelable(builder.build());
 
-        builder.setUnixEpochTime(new TimestampedValue<>(1111L, 2222L));
+        builder.setUnixEpochTime(new UnixEpochTime(1111L, 2222L));
         assertRoundTripParcelable(builder.build());
 
         // DebugInfo should also be stored (but is not checked by equals()
@@ -101,7 +101,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noSlotIndex() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
         TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
@@ -115,17 +115,17 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUnixEpochTime() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--slot_index 0 --reference_time 54321");
+                "--slot_index 0 --elapsed_realtime 54321");
         TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test
     public void testParseCommandLineArg_validSuggestion() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--slot_index 0 --reference_time 54321 --unix_epoch_time 12345");
+                "--slot_index 0 --elapsed_realtime 54321 --unix_epoch_time 12345");
         TelephonyTimeSuggestion expectedSuggestion =
                 new TelephonyTimeSuggestion.Builder(0)
-                        .setUnixEpochTime(new TimestampedValue<>(54321L, 12345L))
+                        .setUnixEpochTime(new UnixEpochTime(54321L, 12345L))
                         .build();
         TelephonyTimeSuggestion actualSuggestion =
                 TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
@@ -135,7 +135,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_unknownArgument() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--slot_index 0 --reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+                "--slot_index 0 --elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
         TelephonyTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 }
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
index e3b3ea7..4f27e99 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -112,7 +112,13 @@
         capacity: Float = 1.0f,
         eventTime: Long = 12345L
     ) {
-        registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
+        registeredListener!!.onBatteryStateChanged(IInputDeviceBatteryState().apply {
+            this.deviceId = deviceId
+            this.updateTime = eventTime
+            this.isPresent = isPresent
+            this.status = status
+            this.capacity = capacity
+        })
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
index 3ecc7ff..bf65af3 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -131,7 +131,7 @@
             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_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
                         0 /* capabilities */)
         };
@@ -150,13 +150,13 @@
 
         Light[] mockedLights = {
             new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(4 /* id */, "Light4", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB)
+                        Light.LIGHT_CAPABILITY_COLOR_RGB)
         };
         mockLights(mockedLights);
 
@@ -204,7 +204,7 @@
                 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_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
+                            Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
                 new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
                             Light.LIGHT_CAPABILITY_BRIGHTNESS)
         };
@@ -239,9 +239,9 @@
     @Test
     public void testLightCapabilities() throws Exception {
         Light light = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                Light.LIGHT_CAPABILITY_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
+                Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
         assertThat(light.getType()).isEqualTo(Light.LIGHT_TYPE_INPUT);
-        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_RGB
+        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_COLOR_RGB
                 | Light.LIGHT_CAPABILITY_BRIGHTNESS);
         assertTrue(light.hasBrightnessControl());
         assertTrue(light.hasRgbControl());
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 32c3a26..91fbe00 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -532,6 +532,56 @@
     }
 
     @Test
+    public void testParseSize() {
+        assertEquals(0L, FileUtils.parseSize("0MB"));
+        assertEquals(1_024L, FileUtils.parseSize("1024b"));
+        assertEquals(-1L, FileUtils.parseSize(" -1 b "));
+        assertEquals(0L, FileUtils.parseSize(" -0 gib "));
+        assertEquals(1_000L, FileUtils.parseSize("1K"));
+        assertEquals(1_000L, FileUtils.parseSize("1KB"));
+        assertEquals(10_000L, FileUtils.parseSize("10KB"));
+        assertEquals(100_000L, FileUtils.parseSize("100KB"));
+        assertEquals(1_000_000L, FileUtils.parseSize("1000KB"));
+        assertEquals(1_024_000L, FileUtils.parseSize("1000KiB"));
+        assertEquals(70_000_000L, FileUtils.parseSize("070M"));
+        assertEquals(70_000_000L, FileUtils.parseSize("070MB"));
+        assertEquals(73_400_320L, FileUtils.parseSize("70MiB"));
+        assertEquals(700_000_000L, FileUtils.parseSize("700000KB"));
+        assertEquals(200_000_000L, FileUtils.parseSize("+200MB"));
+        assertEquals(1_000_000_000L, FileUtils.parseSize("1000MB"));
+        assertEquals(1_000_000_000L, FileUtils.parseSize("+1000 mb"));
+        assertEquals(644_245_094_400L, FileUtils.parseSize("600GiB"));
+        assertEquals(999_000_000_000L, FileUtils.parseSize("999GB"));
+        assertEquals(999_000_000_000L, FileUtils.parseSize("999 gB"));
+        assertEquals(9_999_000_000_000L, FileUtils.parseSize("9999GB"));
+        assertEquals(9_000_000_000_000L, FileUtils.parseSize(" 9000 GB   "));
+        assertEquals(1_234_000_000_000L, FileUtils.parseSize(" 1234 GB  "));
+        assertEquals(1_234_567_890_000L, FileUtils.parseSize(" 1234567890 KB  "));
+    }
+
+    @Test
+    public void testParseSize_invalidArguments() {
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(null));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("null"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(""));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("     "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("KB"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123 dd"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("Invalid"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ABC890 KB  "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-=+90 KB  "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("--123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-KB"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("++123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+ 1 +"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+--+ 1 +"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("1GB+"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" + 1234567890 KB  "));
+    }
+
+    @Test
     public void testTranslateMode() throws Exception {
         assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
 
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index ae4edb9..52846df 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -72,4 +72,7 @@
         assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
     }
 
+    public void testGetAdvertisedMem() {
+        assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
+    }
 }
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 5cad549..d07187c 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -33,7 +33,21 @@
     private int eMethodCalls = 0;
     private int fMethodCalls = 0;
     private int gMethodCalls = 0;
-    
+
+    public void testNullStrings() {
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
+
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
+
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, null, 42);
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
+
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
+        Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, null);
+    }
+
     @SmallTest
     public void testNativeTracingFromJava()
     {
diff --git a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
deleted file mode 100644
index 32fdb5e..0000000
--- a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
+++ /dev/null
@@ -1,407 +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.text;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.text.method.WordIterator;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class LayoutGetRangeForRectTest {
-
-    private static final int WIDTH = 200;
-    private static final int HEIGHT = 1000;
-    private static final String DEFAULT_TEXT = ""
-            // Line 0 (offset 0 to 18)
-            // - Word 0 (offset 0 to 4) has bounds [0, 40], center 20
-            // - Word 1 (offset 5 to 11) has bounds [50, 110], center 80
-            // - Word 2 (offset 12 to 17) has bounds [120, 170], center 145
-            + "XXXX XXXXXX XXXXX "
-            // Line 1 (offset 18 to 36)
-            // - Word 3 (offset 18 to 23) has bounds [0, 50], center 25
-            // - Word 4 (offset 24 to 26, RTL) has bounds [100, 110], center 105
-            // - Word 5 (offset 27 to 29, RTL) has bounds [80, 90], center 85
-            // - Word 6 start part (offset 30 to 32, RTL) has bounds [60, 70], center 65
-            // - Word 6 end part (offset 32 to 35) has bounds [110, 140], center 125
-            + "XXXXX \u05D1\u05D1 \u05D1\u05D1 \u05D1\u05D1XXX\n"
-            // Line 2 (offset 36 to 38)
-            // - Word 7 start part (offset 36 to 38) has bounds [0, 150], center 75
-            // Line 3 (offset 38 to 40)
-            // - Word 7 middle part (offset 38 to 40) has bounds [0, 150], center 75
-            // Line 4 (offset 40 to 46)
-            // - Word 7 end part (offset 40 to 41) has bounds [0, 100], center 50
-            // - Word 8 (offset 42 to 44) has bounds [110, 130], center 120
-            + "CLCLC XX \n";
-
-    private Layout mLayout;
-    private float[] mLineCenters;
-    private GraphemeClusterSegmentIterator mGraphemeClusterSegmentIterator;
-    private WordSegmentIterator mWordSegmentIterator;
-
-    @Before
-    public void setup() {
-        // The test font includes the following characters:
-        // U+0020 ( ): 10em
-        // U+002E (.): 10em
-        // U+0049 (I): 1em
-        // U+0056 (V): 5em
-        // U+0058 (X): 10em
-        // U+004C (L): 50em
-        // U+0043 (C): 100em
-        // U+005F (_): 0em
-        // U+05D0    : 1em  // HEBREW LETTER ALEF
-        // U+05D1    : 5em  // HEBREW LETTER BET
-        // U+FFFD (invalid surrogate will be replaced to this): 7em
-        // U+10331 (\uD800\uDF31): 10em
-        // Undefined : 0.5em
-        TextPaint textPaint = new TextPaint();
-        textPaint.setTypeface(
-                Typeface.createFromAsset(
-                        InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(),
-                        "fonts/StaticLayoutLineBreakingTestFont.ttf"));
-        // Make 1 em equal to 1 pixel.
-        textPaint.setTextSize(1.0f);
-
-        mLayout = StaticLayout.Builder.obtain(
-                DEFAULT_TEXT, 0, DEFAULT_TEXT.length(), textPaint, WIDTH).build();
-
-        mLineCenters = new float[mLayout.getLineCount()];
-        for (int i = 0; i < mLayout.getLineCount(); ++i) {
-            mLineCenters[i] = (mLayout.getLineTop(i) + mLayout.getLineBottomWithoutSpacing(i)) / 2f;
-        }
-
-        mGraphemeClusterSegmentIterator =
-                new GraphemeClusterSegmentIterator(DEFAULT_TEXT, textPaint);
-        WordIterator wordIterator = new WordIterator();
-        wordIterator.setCharSequence(DEFAULT_TEXT, 0, DEFAULT_TEXT.length());
-        mWordSegmentIterator = new WordSegmentIterator(DEFAULT_TEXT, wordIterator);
-    }
-
-    @Test
-    public void getRangeForRect_character() {
-        // Character 1 on line 0 has center 15.
-        // Character 2 on line 0 has center 25.
-        RectF area = new RectF(14f, mLineCenters[0] - 1f, 26f, mLineCenters[0] + 1f);
-
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(1, 3).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_partialCharacterButNotCenter() {
-        // Character 0 on line 0 has center 5.
-        // Character 1 on line 0 has center 15.
-        // Character 2 on line 0 has center 25.
-        // Character 3 on line 0 has center 35.
-        RectF area = new RectF(6f, mLineCenters[0] - 1f, 34f, mLineCenters[0] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        // Area partially overlaps characters 0 and 3 but does not contain their centers.
-        assertThat(range).asList().containsExactly(1, 3).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_rtl() {
-        // Character 25 on line 1 has center 102.5.
-        // Character 26 on line 1 has center 95.
-        RectF area = new RectF(94f, mLineCenters[1] - 1f, 103f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(25, 27).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_ltrAndRtl() {
-        // Character 22 on line 1 has center 45.
-        // The end of the RTL run (offset 24 to 32) on line 1 is at 60.
-        RectF area = new RectF(44f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(22, 32).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_rtlAndLtr() {
-        // The start of the RTL run (offset 24 to 32) on line 1 is at 110.
-        // Character 33 on line 1 has center 125.
-        RectF area = new RectF(93f, mLineCenters[1] - 1f, 131f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(24, 34).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_betweenCharacters_shouldFallback() {
-        // Character 1 on line 0 has center 15.
-        // Character 2 on line 0 has center 25.
-        RectF area = new RectF(16f, mLineCenters[0] - 1f, 24f, mLineCenters[0] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_character_betweenLines_shouldFallback() {
-        // Area top is below the center of line 0.
-        // Area bottom is above the center of line 1.
-        RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        // Area partially covers two lines but does not contain the center of any characters.
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_character_multiLine() {
-        // Character 9 on line 0 has center 95.
-        // Character 42 on line 4 has center 115.
-        RectF area = new RectF(93f, 0, 118f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(9, 43).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_multiLine_betweenCharactersOnSomeLines() {
-        // Character 6 on line 0 has center 65.
-        // Character 7 on line 0 has center 75.
-        // Character 30 on line 1 has center 67.5.
-        // Character 36 on line 2 has center 50.
-        // Character 37 on line 2 has center 125.
-        // Character 38 on line 3 has center 50.
-        // Character 39 on line 3 has center 125.
-        // Character 40 on line 4 has center 50.
-        // Character 41 on line 4 has center 105.
-        RectF area = new RectF(66f, 0, 69f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        // Area crosses all lines but does not contain the center of any characters on lines 0, 2,
-        // 3, or 4. So the only included character is character 30 on line 1.
-        assertThat(range).asList().containsExactly(30, 31).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_character_multiLine_betweenCharactersOnAllLines_shouldFallback() {
-        // Character 6 on line 0 has center 65.
-        // Character 7 on line 0 has center 75.
-        // Character 30 on line 1 has center 67.5.
-        // Character 31 on line 1 has center 62.5.
-        // Character 36 on line 2 has center 50.
-        // Character 37 on line 2 has center 125.
-        // Character 38 on line 3 has center 50.
-        // Character 39 on line 3 has center 125.
-        // Character 40 on line 4 has center 50.
-        // Character 41 on line 4 has center 105.
-        RectF area = new RectF(66f, 0, 67f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        // Area crosses all lines but does not contain the center of any characters.
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_character_all() {
-        // Entire area, should include all text.
-        RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
-
-        assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length()).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word() {
-        // Word 1 (offset 5 to 11) on line 0 has center 80.
-        RectF area = new RectF(79f, mLineCenters[0] - 1f, 81f, mLineCenters[0] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).asList().containsExactly(5, 11).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_partialWordButNotCenter() {
-        // Word 0 (offset 0 to 4) on line 0 has center 20.
-        // Word 1 (offset 5 to 11) on line 0 has center 80.
-        // Word 2 (offset 12 to 17) on line 0 center 145
-        RectF area = new RectF(21f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Area partially overlaps words 0 and 2 but does not contain their centers, so only word 1
-        // is included. Whitespace between words is not included.
-        assertThat(range).asList().containsExactly(5, 11).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_rtl() {
-        // Word 4 (offset 24 to 26, RTL) on line 1 center 105
-        RectF area = new RectF(88f, mLineCenters[1] - 1f, 119f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).asList().containsExactly(24, 26).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_ltrAndRtl() {
-        // Word 3 (offset 18 to 23) on line 1 has center 25
-        // The end of the RTL run (offset 24 to 32) on line 1 is at 60.
-        RectF area = new RectF(24f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Selects all of word 6, not just the first RTL part.
-        assertThat(range).asList().containsExactly(18, 35).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_rtlAndLtr() {
-        // The start of the RTL run (offset 24 to 32) on line 1 is at 110.
-        // End part of word 6 (offset 32 to 35) on line 1 has center 125.
-        RectF area = new RectF(93f, mLineCenters[1] - 1f, 174f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).asList().containsExactly(24, 35).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_betweenWords_shouldFallback() {
-        // Word 1 on line 0 has center 80.
-        // Word 2 on line 0 has center 145.
-        RectF area = new RectF(81f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_word_betweenLines_shouldFallback() {
-        // Area top is below the center of line 0.
-        // Area bottom is above the center of line 1.
-        RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Area partially covers two lines but does not contain the center of any words.
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_word_multiLine() {
-        // Word 1 (offset 5 to 11) on line 0 has center 80.
-        // End part of word 7 (offset 40 to 41) on line 4 has center 50.
-        RectF area = new RectF(42f, 0, 91f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).asList().containsExactly(5, 41).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_multiLine_betweenWordsOnSomeLines() {
-        // Word 1 on line 0 has center 80.
-        // Word 2 on line 0 has center 145.
-        // Word 5 (offset 27 to 29) on line 1 has center 85.
-        // Word 7 on line 2 has center 50.
-        // Word 37 on line 2 has center 125.
-        // Word 38 on line 3 has center 50.
-        // Word 39 on line 3 has center 125.
-        // Word 40 on line 4 has center 50.
-        // Word 41 on line 4 has center 105.
-        RectF area = new RectF(84f, 0, 86f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Area crosses all lines but does not contain the center of any words on lines 0, 2, 3, or
-        // 4. So the only included word is word 5 on line 1.
-        assertThat(range).asList().containsExactly(27, 29).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_multiLine_betweenCharactersOnAllLines_shouldFallback() {
-        // Word 1 on line 0 has center 80.
-        // Word 2 on line 0 has center 145.
-        // Word 4 on line 1 has center 105.
-        // Word 5 on line 1 has center 85.
-        // Word 7 on line 2 has center 50.
-        // Word 37 on line 2 has center 125.
-        // Word 38 on line 3 has center 50.
-        // Word 39 on line 3 has center 125.
-        // Word 40 on line 4 has center 50.
-        // Word 41 on line 4 has center 105.
-        RectF area = new RectF(86f, 0, 89f, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Area crosses all lines but does not contain the center of any words.
-        assertThat(range).isNull();
-    }
-
-    @Test
-    public void getRangeForRect_word_wordSpansMultipleLines_firstPartInsideArea() {
-        // Word 5 (offset 27 to 29) on line 1 has center 85.
-        // First part of word 7 (offset 36 to 38) on line 2 has center 75.
-        RectF area = new RectF(74f, mLineCenters[1] - 1f, 86f, mLineCenters[2] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Selects all of word 7, not just the first part on line 2.
-        assertThat(range).asList().containsExactly(27, 41).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_wordSpansMultipleLines_middlePartInsideArea() {
-        // Middle part of word 7 (offset 38 to 40) on line 2 has center 75.
-        RectF area = new RectF(74f, mLineCenters[3] - 1f, 75f, mLineCenters[3] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Selects all of word 7, not just the middle part on line 3.
-        assertThat(range).asList().containsExactly(36, 41).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_wordSpansMultipleLines_endPartInsideArea() {
-        // End part of word 7 (offset 40 to 41) on line 4 has center 50.
-        // Word 8 (offset 42 to 44) on line 4 has center 120
-        RectF area = new RectF(49f, mLineCenters[4] - 1f, 121f, mLineCenters[4] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Selects all of word 7, not just the middle part on line 3.
-        assertThat(range).asList().containsExactly(36, 44).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_wordSpansMultipleRuns_firstPartInsideArea() {
-        // Word 5 (offset 27 to 29) on line 1 has center 85.
-        // First part of word 6 (offset 30 to 32) on line 1 has center 65.
-        RectF area = new RectF(64f, mLineCenters[1] - 1f, 86f, mLineCenters[1] + 1f);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        // Selects all of word 6, not just the first RTL part.
-        assertThat(range).asList().containsExactly(27, 35).inOrder();
-    }
-
-    @Test
-    public void getRangeForRect_word_all() {
-        // Entire area, should include all text except the last two whitespace characters.
-        RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
-        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
-
-        assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length() - 2).inOrder();
-    }
-}
diff --git a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
new file mode 100644
index 0000000..c8e2364
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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/view/inputmethod/BaseInputConnectionTest.java b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
index 2bb5abe..b3886e8 100644
--- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
@@ -78,25 +78,24 @@
         verifyContent("text1text2text3", 15, 15, -1, -1);
 
         // before commit: "text1text2text3|"
-        // after commit: "text1text2text3text4|"
-        // BUG(b/21476564): this behavior is inconsistent with API description.
+        // after commit: "text1text2text3|text4"
         assertThat(mBaseInputConnection.commitText("text4", 0)).isTrue();
-        verifyContent("text1text2text3text4", 20, 20, -1, -1);
+        verifyContent("text1text2text3text4", 15, 15, -1, -1);
 
-        // before commit: "text1text2text3text4|"
-        // after commit: "text1text2text3text|4text5"
+        // before commit: "text1text2text3|text4"
+        // after commit: "text1text2text|3text5text4"
         assertThat(mBaseInputConnection.commitText("text5", -1)).isTrue();
-        verifyContent("text1text2text3text4text5", 19, 19, -1, -1);
+        verifyContent("text1text2text3text5text4", 14, 14, -1, -1);
 
-        // before commit: "text1text2text3text|4text5"
-        // after commit: "text1text2text3te|xttext64text5"
+        // before commit: "text1text2text|3text5text4"
+        // after commit: "text1text2te|xttext63text5text4"
         assertThat(mBaseInputConnection.commitText("text6", -2)).isTrue();
-        verifyContent("text1text2text3texttext64text5", 17, 17, -1, -1);
+        verifyContent("text1text2texttext63text5text4", 12, 12, -1, -1);
 
-        // before commit: "text1text2text3te|xttext64text5"
-        // after commit: "|text1text2text3tetext7xttext64text5"
+        // before commit: "text1text2te|xttext63text5text4"
+        // after commit: "|text1text2tetext7xttext63text5text4"
         assertThat(mBaseInputConnection.commitText("text7", -100)).isTrue();
-        verifyContent("text1text2text3tetext7xttext64text5", 0, 0, -1, -1);
+        verifyContent("text1text2tetext7xttext63text5text4", 0, 0, -1, -1);
     }
 
     @Test
@@ -296,12 +295,11 @@
         verifyContent("abcdef", 6, 6, 3, 6);
 
         // before set composing text: "abc|"
-        // after set composing text: "abcdef|"
-        //                               ---
-        // BUG(b/21476564): this behavior is inconsistent with API description.
+        // after set composing text: "abc|def"
+        //                                ---
         prepareContent("abc", 3, 3, -1, -1);
         assertThat(mBaseInputConnection.setComposingText("def", 0)).isTrue();
-        verifyContent("abcdef", 6, 6, 3, 6);
+        verifyContent("abcdef", 3, 3, 3, 6);
 
         // before set composing text: "abc|"
         // after set composing text: "ab|cdef"
@@ -619,6 +617,74 @@
         verifyTextSnapshotContentEquals(mBaseInputConnection.takeSnapshot(), expectedTextSnapshot);
     }
 
+    @Test
+    public void testReplaceText_toEditorWithoutSelectionAndComposing() {
+        // before replace: "|"
+        // after replace: "text1|"
+        assertThat(mBaseInputConnection.replaceText(0, 0, "text1", 1, null)).isTrue();
+        verifyContent("text1", 5, 5, -1, -1);
+
+        // before replace: "text1|"
+        // after replace: "text2|"
+        assertThat(mBaseInputConnection.replaceText(0, 5, "text2", 1, null)).isTrue();
+        verifyContent("text2", 5, 5, -1, -1);
+
+        // before replace: "text1|"
+        // after replace: "|text3"
+        assertThat(mBaseInputConnection.replaceText(0, 5, "text3", -1, null)).isTrue();
+        verifyContent("text3", 0, 0, -1, -1);
+
+        // before replace: "|text3"
+        // after replace: "ttext4|t3"
+        // BUG(b/21476564): this behavior is inconsistent with API description.
+        assertThat(mBaseInputConnection.replaceText(1, 3, "text4", 1, null)).isTrue();
+        verifyContent("ttext4t3", 6, 6, -1, -1);
+
+        // before replace: "ttext4|t3"
+        // after replace: "|text5t3"
+        assertThat(mBaseInputConnection.replaceText(0, 6, "text5", -1, null)).isTrue();
+        verifyContent("text5t3", 0, 0, -1, -1);
+    }
+
+    @Test
+    public void testReplaceText_toEditorWithSelection() {
+        // before replace: "123|456|789"
+        // before replace: "123text|6789"
+        prepareContent("123456789", 3, 6, -1, -1);
+        assertThat(mBaseInputConnection.replaceText(3, 5, "text", 1, null)).isTrue();
+        verifyContent("123text6789", 7, 7, -1, -1);
+
+        // before replace: "|123|"
+        // before replace: "|text23"
+        prepareContent("123", 0, 3, -1, -1);
+        assertThat(mBaseInputConnection.replaceText(0, 1, "text", 0, null)).isTrue();
+        verifyContent("text23", 0, 0, -1, -1);
+    }
+
+    @Test
+    public void testReplaceText_toEditorWithComposing() {
+        // before replace: "123456|789"
+        //                     ---
+        // before replace: "123456text|"
+        prepareContent("123456789", 6, 6, 3, 6);
+        assertThat(mBaseInputConnection.replaceText(6, 9, "text", 1, null)).isTrue();
+        verifyContent("123456text", 10, 10, -1, -1);
+
+        // before replace: "123456789|"
+        //                     ---
+        // before replace: "text|123456789"
+        prepareContent("123456789", 9, 9, 3, 6);
+        assertThat(mBaseInputConnection.replaceText(0, 0, "text", 1, null)).isTrue();
+        verifyContent("text123456789", 4, 4, -1, -1);
+
+        // before replace: "|123456789|"
+        //                      ---
+        // before replace: "12text|9"
+        prepareContent("123456789", 0, 9, 3, 6);
+        assertThat(mBaseInputConnection.replaceText(2, 8, "text", 1, null)).isTrue();
+        verifyContent("12text9", 6, 6, -1, -1);
+    }
+
     private void prepareContent(
             CharSequence text,
             int selectionStart,
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
index bbbc423..d6145eb 100644
--- a/core/tests/coretests/src/android/window/BackNavigationTest.java
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -92,7 +92,7 @@
         try {
             mInstrumentation.getUiAutomation().waitForIdle(500, 1000);
             BackNavigationInfo info = ActivityTaskManager.getService()
-                    .startBackNavigation(true, null);
+                    .startBackNavigation(null, null);
             assertNotNull("BackNavigationInfo is null", info);
             assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback());
             info.getOnBackInvokedCallback().onBackInvoked();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
index 56a7070..2861428 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
@@ -46,14 +46,14 @@
     }
 
     @Override
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelWrapperTask(info, holder);
+    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+        return new LoadLabelWrapperTask(info);
     }
 
     class LoadLabelWrapperTask extends LoadLabelTask {
 
-        protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
-            super(dri, holder);
+        protected LoadLabelWrapperTask(DisplayResolveInfo dri) {
+            super(dri);
         }
 
         @Override
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
new file mode 100644
index 0000000..81cc9d8
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class ProcessCpuTrackerTest {
+    @Test
+    public void testGetCpuTime() throws Exception {
+        final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
+        assertThat(tracker.getCpuTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
+    }
+
+    @Test
+    public void testGetCpuDelayTime() throws Exception {
+        final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
+        assertThat(tracker.getCpuDelayTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
+    }
+}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 4c247427..9e39e13 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -16,11 +16,12 @@
 
 package android.hardware.devicestate;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -36,7 +37,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -59,6 +59,7 @@
     public void setUp() {
         mService = new TestDeviceStateManagerService();
         mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService);
+        assertFalse(mService.mCallbacks.isEmpty());
     }
 
     @Test
@@ -79,8 +80,8 @@
         verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback2).onStateChanged(eq(mService.getMergedState()));
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the supported states and verify callback
         mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE });
@@ -88,8 +89,8 @@
         verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
         mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE });
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the base state and verify callback
         mService.setBaseState(OTHER_DEVICE_STATE);
@@ -98,8 +99,8 @@
         verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback2).onStateChanged(eq(mService.getMergedState()));
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the requested state and verify callback
         DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
@@ -120,7 +121,7 @@
         verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates()));
         verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback).onStateChanged(eq(mService.getMergedState()));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback);
 
@@ -130,29 +131,19 @@
     }
 
     @Test
-    public void submittingRequestRegistersCallback() {
-        assertTrue(mService.mCallbacks.isEmpty());
-
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
-        mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
-
-        assertFalse(mService.mCallbacks.isEmpty());
-    }
-
-    @Test
     public void submitRequest() {
         DeviceStateCallback callback = mock(DeviceStateCallback.class);
         mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
         verify(callback).onStateChanged(eq(mService.getBaseState()));
-        Mockito.reset(callback);
+        reset(callback);
 
         DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
         mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
 
         verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
@@ -160,6 +151,69 @@
     }
 
     @Test
+    public void submitBaseStateOverrideRequest() {
+        DeviceStateCallback callback = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+                ConcurrentUtils.DIRECT_EXECUTOR);
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        reset(callback);
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+                null /* callback */);
+
+        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+    }
+
+    @Test
+    public void submitBaseAndEmulatedStateOverride() {
+        DeviceStateCallback callback = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+                ConcurrentUtils.DIRECT_EXECUTOR);
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        reset(callback);
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+                null /* callback */);
+
+        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+        reset(callback);
+
+        DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
+                DEFAULT_DEVICE_STATE).build();
+
+        mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
+
+        assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+        verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE));
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelStateRequest();
+
+        verify(callback).onStateChanged(OTHER_DEVICE_STATE);
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+        verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE);
+        verify(callback).onStateChanged(DEFAULT_DEVICE_STATE);
+    }
+
+    @Test
     public void verifyDeviceStateRequestCallbacksCalled() {
         DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
 
@@ -169,7 +223,7 @@
                 callback /* callback */);
 
         verify(callback).onRequestActivated(eq(request));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
@@ -203,13 +257,16 @@
         private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
         private int mBaseState = DEFAULT_DEVICE_STATE;
         private Request mRequest;
+        private Request mBaseStateRequest;
 
         private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
 
         private DeviceStateInfo getInfo() {
+            final int mergedBaseState = mBaseStateRequest == null
+                    ? mBaseState : mBaseStateRequest.state;
             final int mergedState = mRequest == null
-                    ? mBaseState : mRequest.state;
-            return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
+                    ? mergedBaseState : mRequest.state;
+            return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState);
         }
 
         private void notifyDeviceStateInfoChanged() {
@@ -238,7 +295,7 @@
             try {
                 callback.onDeviceStateInfoChanged(getInfo());
             } catch (RemoteException e) {
-                // Do nothing. Should never happen.
+                e.rethrowFromSystemServer();
             }
         }
 
@@ -249,7 +306,7 @@
                     try {
                         callback.onRequestCanceled(mRequest.token);
                     } catch (RemoteException e) {
-                        // Do nothing. Should never happen.
+                        e.rethrowFromSystemServer();
                     }
                 }
             }
@@ -262,7 +319,7 @@
                 try {
                     callback.onRequestActive(token);
                 } catch (RemoteException e) {
-                    // Do nothing. Should never happen.
+                    e.rethrowFromSystemServer();
                 }
             }
         }
@@ -275,7 +332,46 @@
                 try {
                     callback.onRequestCanceled(token);
                 } catch (RemoteException e) {
-                    // Do nothing. Should never happen.
+                    e.rethrowFromSystemServer();
+                }
+            }
+            notifyDeviceStateInfoChanged();
+        }
+
+        @Override
+        public void requestBaseStateOverride(IBinder token, int state, int flags) {
+            if (mBaseStateRequest != null) {
+                for (IDeviceStateManagerCallback callback : mCallbacks) {
+                    try {
+                        callback.onRequestCanceled(mBaseStateRequest.token);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+
+            final Request request = new Request(token, state, flags);
+            mBaseStateRequest = request;
+            notifyDeviceStateInfoChanged();
+
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestActive(token);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void cancelBaseStateOverride() throws RemoteException {
+            IBinder token = mBaseStateRequest.token;
+            mBaseStateRequest = null;
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestCanceled(token);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
                 }
             }
             notifyDeviceStateInfoChanged();
@@ -296,7 +392,7 @@
         }
 
         public int getBaseState() {
-            return mBaseState;
+            return getInfo().baseState;
         }
 
         public int getMergedState() {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 392d89e..8ca1607 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -483,6 +483,8 @@
         <permission name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE" />
         <!-- Permission required for GTS test - GtsAssistIntentTestCases -->
         <permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
+        <!-- Permission required for test - CellBroadcastComplianceTest -->
+        <permission name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" />
         <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
         <permission name="android.permission.LOCK_DEVICE" />
         <!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 682483d9..f9f2906 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -373,12 +373,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/TransitionController.java"
     },
-    "-1750384749": {
-      "message": "Launch on display check: allow launch on public display",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TASKS",
-      "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
-    },
     "-1750206390": {
       "message": "Exception thrown when creating surface for client %s (%s). %s",
       "level": "WARN",
@@ -907,6 +901,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1253056469": {
+      "message": "Launch on display check: %s launch for userId=%d on displayId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TASKS",
+      "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
+    },
     "-1248645819": {
       "message": "\tAdd container=%s",
       "level": "DEBUG",
@@ -1111,6 +1111,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1033630971": {
+      "message": "onBackNavigationDone backType=%s, triggerBack=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "-1022146708": {
       "message": "Skipping %s: mismatch activity type",
       "level": "DEBUG",
@@ -3115,6 +3121,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "829869827": {
+      "message": "Cannot launch dream activity due to invalid state. dreaming: %b packageName: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_DREAM",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "835814848": {
       "message": "%s",
       "level": "INFO",
@@ -3985,12 +3997,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1778919449": {
-      "message": "onBackNavigationDone backType=%s, task=%s, prevActivity=%s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_BACK_PREVIEW",
-      "at": "com\/android\/server\/wm\/BackNavigationController.java"
-    },
     "1781673113": {
       "message": "onAnimationFinished(): targetRootTask=%s targetActivity=%s mRestoreTargetBehindRootTask=%s",
       "level": "DEBUG",
@@ -4141,6 +4147,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
+    "1918771553": {
+      "message": "Dream packageName does not match active dream. Package %s does not match %s or %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_DREAM",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1921821199": {
       "message": "Preserving %s until the new one is added",
       "level": "VERBOSE",
@@ -4383,6 +4395,9 @@
     "WM_DEBUG_DRAW": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_DREAM": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_FOCUS": {
       "tag": "WindowManager"
     },
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index ca3c847..31df474 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -24,7 +24,7 @@
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SuppressLint;
 import android.hardware.DataSpace;
-import android.hardware.DataSpace.NamedDataSpace;
+import android.hardware.DataSpace.ColorDataSpace;
 import android.util.SparseIntArray;
 
 import libcore.util.NativeAllocationRegistry;
@@ -1406,7 +1406,7 @@
      */
     @SuppressLint("MethodNameUnits")
     @Nullable
-    public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+    public static ColorSpace getFromDataSpace(@ColorDataSpace int dataSpace) {
         int index = sDataToColorSpaces.get(dataSpace, -1);
         if (index != -1) {
             return ColorSpace.get(index);
@@ -1425,7 +1425,7 @@
      * @return the dataspace value.
      */
     @SuppressLint("MethodNameUnits")
-    public @NamedDataSpace int getDataSpace() {
+    public @ColorDataSpace int getDataSpace() {
         int index = sDataToColorSpaces.indexOfValue(getId());
         if (index != -1) {
             return sDataToColorSpaces.keyAt(index);
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 0e67f1f..c6731d1 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -47,9 +47,7 @@
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Optional;
 import java.util.concurrent.Executor;
-import java.util.stream.Stream;
 
 import sun.misc.Cleaner;
 
@@ -1142,6 +1140,16 @@
     }
 
     /**
+     * Sets whether or not the current process is a system or persistent process. Used to influence
+     * the chosen memory usage policy.
+     *
+     * @hide
+     **/
+    public static void setIsSystemOrPersistent() {
+        nSetIsSystemOrPersistent(true);
+    }
+
+    /**
      * Returns true if HardwareRender will produce output.
      *
      * This value is global to the process and affects all uses of HardwareRenderer,
@@ -1204,30 +1212,6 @@
     private static class ProcessInitializer {
         static ProcessInitializer sInstance = new ProcessInitializer();
 
-        // Magic values from android/data_space.h
-        private static final int INTERNAL_DATASPACE_SRGB = 142671872;
-        private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
-        private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
-
-        private enum Dataspace {
-            DISPLAY_P3(ColorSpace.Named.DISPLAY_P3, INTERNAL_DATASPACE_DISPLAY_P3),
-            SCRGB(ColorSpace.Named.EXTENDED_SRGB, INTERNAL_DATASPACE_SCRGB),
-            SRGB(ColorSpace.Named.SRGB, INTERNAL_DATASPACE_SRGB);
-
-            private final ColorSpace.Named mColorSpace;
-            private final int mNativeDataspace;
-            Dataspace(ColorSpace.Named colorSpace, int nativeDataspace) {
-                this.mColorSpace = colorSpace;
-                this.mNativeDataspace = nativeDataspace;
-            }
-
-            static Optional<Dataspace> find(ColorSpace colorSpace) {
-                return Stream.of(Dataspace.values())
-                        .filter(d -> ColorSpace.get(d.mColorSpace).equals(colorSpace))
-                        .findFirst();
-            }
-        }
-
         private boolean mInitialized = false;
         private boolean mDisplayInitialized = false;
 
@@ -1296,6 +1280,7 @@
             initDisplayInfo();
 
             nSetIsHighEndGfx(ActivityManager.isHighEndGfx());
+            nSetIsLowRam(ActivityManager.isLowRamDeviceStatic());
             // Defensively clear out the context in case we were passed a context that can leak
             // if we live longer than it, e.g. an activity context.
             mContext = null;
@@ -1314,26 +1299,55 @@
                 return;
             }
 
-            Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-            if (display == null) {
+            final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
+            if (defaultDisplay == null) {
                 Log.d(LOG_TAG, "Failed to find default display for display-based configuration");
                 return;
             }
 
-            Dataspace wideColorDataspace =
-                    Optional.ofNullable(display.getPreferredWideGamutColorSpace())
-                            .flatMap(Dataspace::find)
-                            // Default to SRGB if the display doesn't support wide color
-                            .orElse(Dataspace.SRGB);
+            final Display[] allDisplays = dm.getDisplays();
+            if (allDisplays.length == 0) {
+                Log.d(LOG_TAG, "Failed to query displays");
+                return;
+            }
 
-            // Grab the physical screen dimensions from the active display mode
-            // Strictly speaking the screen resolution may not always be constant - it is for
-            // sizing the font cache for the underlying rendering thread. Since it's a
-            // heuristic we don't need to be always 100% correct.
-            Mode activeMode = display.getMode();
-            nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
-                    display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
-                    display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
+            final Mode activeMode = defaultDisplay.getMode();
+            final ColorSpace defaultWideColorSpace =
+                    defaultDisplay.getPreferredWideGamutColorSpace();
+            int wideColorDataspace = defaultWideColorSpace != null
+                    ? defaultWideColorSpace.getDataSpace() : 0;
+            // largest width & height are used to size the default HWUI cache sizes. So find the
+            // largest display resolution we could encounter & use that as the guidance. The actual
+            // memory policy in play will interpret these values differently.
+            int largestWidth = activeMode.getPhysicalWidth();
+            int largestHeight = activeMode.getPhysicalHeight();
+
+            for (int i = 0; i < allDisplays.length; i++) {
+                final Display display = allDisplays[i];
+                // Take the first wide gamut dataspace as the source of truth
+                // Possibly should do per-HardwareRenderer wide gamut dataspace so we can use the
+                // target display's ideal instead
+                if (wideColorDataspace == 0) {
+                    ColorSpace cs = display.getPreferredWideGamutColorSpace();
+                    if (cs != null) {
+                        wideColorDataspace = cs.getDataSpace();
+                    }
+                }
+                Mode[] modes = display.getSupportedModes();
+                for (int j = 0; j < modes.length; j++) {
+                    Mode mode = modes[j];
+                    int width = mode.getPhysicalWidth();
+                    int height = mode.getPhysicalHeight();
+                    if ((width * height) > (largestWidth * largestHeight)) {
+                        largestWidth = width;
+                        largestHeight = height;
+                    }
+                }
+            }
+
+            nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
+                    wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
+                    defaultDisplay.getPresentationDeadlineNanos());
 
             mDisplayInitialized = true;
         }
@@ -1418,6 +1432,10 @@
 
     private static native void nSetIsHighEndGfx(boolean isHighEndGfx);
 
+    private static native void nSetIsLowRam(boolean isLowRam);
+
+    private static native void nSetIsSystemOrPersistent(boolean isSystemOrPersistent);
+
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
 
     private static native void nDestroy(long nativeProxy, long rootRenderNode);
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 25e1922..3b7d0e1 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -195,6 +195,8 @@
     @UnsupportedAppUsage
     public final long native_instance;
 
+    private final String mSystemFontFamilyName;
+
     private final Runnable mCleaner;
 
     /** @hide */
@@ -272,6 +274,14 @@
     }
 
     /**
+     * Returns the system font family name if the typeface was created from a system font family,
+     * otherwise returns null.
+     */
+    public final @Nullable String getSystemFontFamilyName() {
+        return mSystemFontFamilyName;
+    }
+
+    /**
      * Returns true if the system has the font family with the name [familyName]. For example
      * querying with "sans-serif" would check if the "sans-serif" family is defined in the system
      * and return true if does.
@@ -870,7 +880,7 @@
             final int italic =
                     (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ?  0 : 1;
             return new Typeface(nativeCreateFromArray(
-                    ptrArray, fallbackTypeface.native_instance, weight, italic));
+                    ptrArray, fallbackTypeface.native_instance, weight, italic), null);
         }
     }
 
@@ -935,7 +945,8 @@
                 }
             }
 
-            typeface = new Typeface(nativeCreateFromTypeface(ni, style));
+            typeface = new Typeface(nativeCreateFromTypeface(ni, style),
+                    family.getSystemFontFamilyName());
             styles.put(style, typeface);
         }
         return typeface;
@@ -1003,7 +1014,8 @@
             }
 
             typeface = new Typeface(
-                    nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
+                    nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic),
+                    base.getSystemFontFamilyName());
             innerCache.put(key, typeface);
         }
         return typeface;
@@ -1013,7 +1025,10 @@
     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
             @NonNull List<FontVariationAxis> axes) {
         final Typeface base = family == null ? Typeface.DEFAULT : family;
-        return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes));
+        Typeface typeface = new Typeface(
+                nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
+                base.getSystemFontFamilyName());
+        return typeface;
     }
 
     /**
@@ -1108,7 +1123,7 @@
         }
         return new Typeface(nativeCreateFromArray(
                 ptrArray, 0, RESOLVE_BY_FONT_TABLE,
-                RESOLVE_BY_FONT_TABLE));
+                RESOLVE_BY_FONT_TABLE), null);
     }
 
     /**
@@ -1116,13 +1131,14 @@
      *
      * @param families array of font families
      */
-    private static Typeface createFromFamilies(@Nullable FontFamily[] families) {
+    private static Typeface createFromFamilies(@NonNull String familyName,
+            @Nullable FontFamily[] families) {
         final long[] ptrArray = new long[families.length];
         for (int i = 0; i < families.length; ++i) {
             ptrArray[i] = families[i].getNativePtr();
         }
         return new Typeface(nativeCreateFromArray(ptrArray, 0,
-                  RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+                  RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), familyName);
     }
 
     /**
@@ -1162,12 +1178,17 @@
             ptrArray[i] = families[i].mNativePtr;
         }
         return new Typeface(nativeCreateFromArray(
-                ptrArray, fallbackTypeface.native_instance, weight, italic));
+                ptrArray, fallbackTypeface.native_instance, weight, italic), null);
     }
 
     // don't allow clients to call this directly
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private Typeface(long ni) {
+        this(ni, null);
+    }
+
+    // don't allow clients to call this directly
+    private Typeface(long ni, @Nullable String systemFontFamilyName) {
         if (ni == 0) {
             throw new RuntimeException("native typeface cannot be made");
         }
@@ -1176,6 +1197,7 @@
         mCleaner = sRegistry.registerNativeAllocation(this, native_instance);
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
+        mSystemFontFamilyName = systemFontFamilyName;
     }
 
     /**
@@ -1200,7 +1222,8 @@
             List<FontConfig.Alias> aliases,
             Map<String, Typeface> outSystemFontMap) {
         for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
-            outSystemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
+            outSystemFontMap.put(entry.getKey(),
+                    createFromFamilies(entry.getKey(), entry.getValue()));
         }
 
         for (int i = 0; i < aliases.size(); ++i) {
@@ -1215,8 +1238,8 @@
                 continue;
             }
             final int weight = alias.getWeight();
-            final Typeface newFace = weight == 400 ? base :
-                    new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
+            final Typeface newFace = weight == 400 ? base : new Typeface(
+                    nativeCreateWeightAlias(base.native_instance, weight), alias.getName());
             outSystemFontMap.put(alias.getName(), newFace);
         }
     }
@@ -1288,7 +1311,7 @@
         buffer.position(buffer.position() + typefacesBytesCount);
         for (long nativePtr : nativePtrs) {
             String name = readString(buffer);
-            out.put(name, new Typeface(nativePtr));
+            out.put(name, new Typeface(nativePtr, name));
         }
         return nativePtrs;
     }
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index c2cd6ff..f507d76 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import java.util.Calendar;
+import java.util.Objects;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -137,13 +138,13 @@
         return new KeyStore2();
     }
 
-    private synchronized IKeystoreService getService(boolean retryLookup) {
+    @NonNull private synchronized IKeystoreService getService(boolean retryLookup) {
         if (mBinder == null || retryLookup) {
             mBinder = IKeystoreService.Stub.asInterface(ServiceManager
                     .getService(KEYSTORE2_SERVICE_NAME));
             Binder.allowBlocking(mBinder.asBinder());
         }
-        return mBinder;
+        return Objects.requireNonNull(mBinder);
     }
 
     void delete(KeyDescriptor descriptor) throws KeyStoreException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
index b631999..4e73bd9 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
@@ -18,13 +18,19 @@
 
 import android.annotation.NonNull;
 import android.security.KeyStoreSecurityLevel;
+import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyMetadata;
 
+import java.security.AlgorithmParameters;
+import java.security.NoSuchAlgorithmException;
 import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
+import java.security.spec.InvalidParameterSpecException;
 
 /**
  * {@link ECPublicKey} backed by keystore.
@@ -56,11 +62,45 @@
         }
     }
 
+    private static String getEcCurveFromKeymaster(int ecCurve) {
+        switch (ecCurve) {
+            case android.hardware.security.keymint.EcCurve.P_224:
+                return "secp224r1";
+            case android.hardware.security.keymint.EcCurve.P_256:
+                return "secp256r1";
+            case android.hardware.security.keymint.EcCurve.P_384:
+                return "secp384r1";
+            case android.hardware.security.keymint.EcCurve.P_521:
+                return "secp521r1";
+        }
+        return "";
+    }
+
+    private ECParameterSpec getCurveSpec(String name)
+            throws NoSuchAlgorithmException, InvalidParameterSpecException {
+        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
+        parameters.init(new ECGenParameterSpec(name));
+        return parameters.getParameterSpec(ECParameterSpec.class);
+    }
+
     @Override
     public AndroidKeyStorePrivateKey getPrivateKey() {
+        ECParameterSpec params = mParams;
+        for (Authorization a : getAuthorizations()) {
+            try {
+                if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
+                    params = getCurveSpec(getEcCurveFromKeymaster(
+                            a.keyParameter.value.getEcCurve()));
+                    break;
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("Unable to parse EC curve "
+                        + a.keyParameter.value.getEcCurve());
+            }
+        }
         return new AndroidKeyStoreECPrivateKey(
                 getUserKeyDescriptor(), getKeyIdDescriptor().nspace, getAuthorizations(),
-                getSecurityLevel(), mParams);
+                getSecurityLevel(), params);
     }
 
     @Override
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
index b1338d1..4caa47f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -31,6 +31,8 @@
 import java.security.ProviderException;
 import java.security.PublicKey;
 import java.security.SecureRandom;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.XECKey;
 import java.security.spec.AlgorithmParameterSpec;
 import java.util.ArrayList;
 import java.util.List;
@@ -132,6 +134,15 @@
             throw new InvalidKeyException("key == null");
         } else if (!(key instanceof PublicKey)) {
             throw new InvalidKeyException("Only public keys supported. Key: " + key);
+        } else if (!(mKey instanceof ECKey && key instanceof ECKey)
+                && !(mKey instanceof XECKey && key instanceof XECKey)) {
+            throw new InvalidKeyException(
+                    "Public and Private key should be of the same type:");
+        } else if (mKey instanceof ECKey
+                && !((ECKey) key).getParams().getCurve()
+                .equals(((ECKey) mKey).getParams().getCurve())) {
+            throw new InvalidKeyException(
+                    "Public and Private key parameters should be same.");
         } else if (!lastPhase) {
             throw new IllegalStateException(
                     "Only one other party supported. lastPhase must be set to true.");
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPrivateKey.java
index 42589640..e392c8d 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPrivateKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPrivateKey.java
@@ -22,16 +22,18 @@
 import android.system.keystore2.KeyDescriptor;
 
 import java.security.PrivateKey;
-import java.security.interfaces.EdECKey;
+import java.security.interfaces.XECPrivateKey;
 import java.security.spec.NamedParameterSpec;
+import java.util.Optional;
 
 /**
  * X25519 Private Key backed by Keystore.
- * instance of {@link PrivateKey} and {@link EdECKey}
+ * instance of {@link PrivateKey} and {@link XECPrivateKey}
  *
  * @hide
  */
-public class AndroidKeyStoreXDHPrivateKey extends AndroidKeyStorePrivateKey implements EdECKey {
+public class AndroidKeyStoreXDHPrivateKey extends AndroidKeyStorePrivateKey
+        implements XECPrivateKey {
     public AndroidKeyStoreXDHPrivateKey(
             @NonNull KeyDescriptor descriptor, long keyId,
             @NonNull Authorization[] authorizations,
@@ -44,4 +46,12 @@
     public NamedParameterSpec getParams() {
         return NamedParameterSpec.X25519;
     }
+
+    @Override
+    public Optional<byte[]> getScalar() {
+        /* An empty Optional if the scalar cannot be extracted (e.g. if the provider is a hardware
+         * token and the private key is not allowed to leave the crypto boundary).
+         */
+        return Optional.empty();
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 7e9c418..fb0a9db 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -41,7 +41,7 @@
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 2;
+        return 1;
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index febd791..74303e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -31,6 +31,7 @@
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -93,6 +94,7 @@
     }
 
     /** No longer overrides the animation if the transition is on the given Task. */
+    @GuardedBy("mLock")
     void stopOverrideSplitAnimation(int taskId) {
         if (mAnimationController != null) {
             mAnimationController.unregisterRemoteAnimations(taskId);
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 c8ac0fc..00be5a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,8 +17,10 @@
 package androidx.window.extensions.embedding;
 
 import android.app.Activity;
+import android.content.res.Configuration;
 import android.util.Pair;
 import android.util.Size;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 
@@ -32,14 +34,18 @@
     private final TaskFragmentContainer mSecondaryContainer;
     @NonNull
     private final SplitRule mSplitRule;
+    @NonNull
+    private SplitAttributes mSplitAttributes;
 
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
-            @NonNull SplitRule splitRule) {
+            @NonNull SplitRule splitRule,
+            @NonNull SplitAttributes splitAttributes) {
         mPrimaryContainer = primaryContainer;
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
+        mSplitAttributes = splitAttributes;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -72,6 +78,26 @@
         return mSplitRule;
     }
 
+    @NonNull
+    SplitAttributes getSplitAttributes() {
+        return mSplitAttributes;
+    }
+
+    /**
+     * Updates the {@link SplitAttributes} to this container.
+     * It is usually used when there's a folding state change or
+     * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
+     * Configuration)}.
+     */
+    void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+        mSplitAttributes = splitAttributes;
+    }
+
+    @NonNull
+    TaskContainer getTaskContainer() {
+        return getPrimaryContainer().getTaskContainer();
+    }
+
     /** Returns the minimum dimension pair of primary container and secondary container. */
     @NonNull
     Pair<Size, Size> getMinDimensionsPair() {
@@ -141,6 +167,7 @@
                 + " primaryContainer=" + mPrimaryContainer
                 + " secondaryContainer=" + mSecondaryContainer
                 + " splitRule=" + mSplitRule
+                + " splitAttributes" + mSplitAttributes
                 + "}";
     }
 }
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 126f835..203ece0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
@@ -40,12 +41,13 @@
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
+import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
+import android.app.Application;
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -62,19 +64,25 @@
 import android.util.Pair;
 import android.util.Size;
 import android.util.SparseArray;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -96,6 +104,23 @@
     @GuardedBy("mLock")
     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
     /**
+     * A developer-defined {@link SplitAttributes} calculator to compute the current
+     * {@link SplitAttributes} with the current device and window states.
+     * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
+     * and unregistered via {@link #clearSplitAttributesCalculator()}.
+     * This is called when:
+     * <ul>
+     *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
+     *     WindowContainerTransaction)}</li>
+     *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
+     *   <li>Checking whether the place holder should be launched if there's a Activity matches
+     *   {@link SplitPlaceholderRule} </li>
+     * </ul>
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private SplitAttributesCalculator mSplitAttributesCalculator;
+    /**
      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
      * below it.
      * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -105,26 +130,65 @@
     @GuardedBy("mLock")
     final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
 
-    // Callback to Jetpack to notify about changes to split states.
-    @NonNull
+    /** Callback to Jetpack to notify about changes to split states. */
+    @Nullable
     private Consumer<List<SplitInfo>> mEmbeddingCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
     private final Handler mHandler;
     final Object mLock = new Object();
     private final ActivityStartMonitor mActivityStartMonitor;
+    @NonNull
+    final WindowLayoutComponentImpl mWindowLayoutComponent;
 
     public SplitController() {
+        this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider
+                .getWindowExtensions().getWindowLayoutComponent()));
+    }
+
+    @VisibleForTesting
+    SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) {
         final MainThreadExecutor executor = new MainThreadExecutor();
         mHandler = executor.mHandler;
         mPresenter = new SplitPresenter(executor, this);
-        ActivityThread activityThread = ActivityThread.currentActivityThread();
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        final Application application = activityThread.getApplication();
         // Register a callback to be notified about activities being created.
-        activityThread.getApplication().registerActivityLifecycleCallbacks(
-                new LifecycleCallbacks());
+        application.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
         // Intercept activity starts to route activities to new containers if necessary.
         Instrumentation instrumentation = activityThread.getInstrumentation();
+
         mActivityStartMonitor = new ActivityStartMonitor();
         instrumentation.addMonitor(mActivityStartMonitor);
+        mWindowLayoutComponent = windowLayoutComponent;
+        mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
+    }
+
+    private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> {
+        @Override
+        public void accept(List<CommonFoldingFeature> foldingFeatures) {
+            synchronized (mLock) {
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                for (int i = 0; i < mTaskContainers.size(); i++) {
+                    final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+                    if (!taskContainer.isVisible()) {
+                        continue;
+                    }
+                    if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) {
+                        continue;
+                    }
+                    // TODO(b/238948678): Support reporting display features in all windowing modes.
+                    if (taskContainer.isInMultiWindow()) {
+                        continue;
+                    }
+                    if (taskContainer.isEmpty()) {
+                        continue;
+                    }
+                    updateContainersInTask(wct, taskContainer);
+                    updateAnimationOverride(taskContainer);
+                }
+                mPresenter.applyTransaction(wct);
+            }
+        }
     }
 
     /** Updates the embedding rules applied to future activity launches. */
@@ -141,12 +205,22 @@
 
     @Override
     public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
-        // TODO: Implement this method
+        synchronized (mLock) {
+            mSplitAttributesCalculator = calculator;
+        }
     }
 
     @Override
     public void clearSplitAttributesCalculator() {
-        // TODO: Implement this method
+        synchronized (mLock) {
+            mSplitAttributesCalculator = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    SplitAttributesCalculator getSplitAttributesCalculator() {
+        return mSplitAttributesCalculator;
     }
 
     @NonNull
@@ -191,7 +265,8 @@
                         onTaskFragmentVanished(wct, info);
                         break;
                     case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
-                        onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
+                        onTaskFragmentParentInfoChanged(wct, taskId,
+                                change.getTaskFragmentParentInfo());
                         break;
                     case TYPE_TASK_FRAGMENT_ERROR:
                         final Bundle errorBundle = change.getErrorBundle();
@@ -346,22 +421,33 @@
      *
      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
      * @param taskId    Id of the parent Task that is changed.
-     * @param parentConfig  Config of the parent Task.
+     * @param parentInfo  {@link TaskFragmentParentInfo} of the parent Task.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
     void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
-            int taskId, @NonNull Configuration parentConfig) {
-        onTaskConfigurationChanged(taskId, parentConfig);
-        if (isInPictureInPicture(parentConfig)) {
-            // No need to update presentation in PIP until the Task exit PIP.
-            return;
-        }
+            int taskId, @NonNull TaskFragmentParentInfo parentInfo) {
         final TaskContainer taskContainer = getTaskContainer(taskId);
         if (taskContainer == null || taskContainer.isEmpty()) {
             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
             return;
         }
+        taskContainer.updateTaskFragmentParentInfo(parentInfo);
+        if (!taskContainer.isVisible()) {
+            // Don't update containers if the task is not visible. We only update containers when
+            // parentInfo#isVisibleRequested is true.
+            return;
+        }
+        onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
+        if (isInPictureInPicture(parentInfo.getConfiguration())) {
+            // No need to update presentation in PIP until the Task exit PIP.
+            return;
+        }
+        updateContainersInTask(wct, taskContainer);
+    }
+
+    private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
+            @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 =
@@ -486,6 +572,7 @@
     }
 
     /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+    @GuardedBy("mLock")
     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -501,14 +588,11 @@
         }
     }
 
-    private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) {
-        final TaskContainer taskContainer = mTaskContainers.get(taskId);
-        if (taskContainer == null) {
-            return;
-        }
+    @GuardedBy("mLock")
+    private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
+            @NonNull Configuration config) {
         final boolean wasInPip = taskContainer.isInPictureInPicture();
         final boolean isInPIp = isInPictureInPicture(config);
-        taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode());
 
         // We need to check the animation override when enter/exit PIP or has bounds changed.
         boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
@@ -526,37 +610,49 @@
      * Updates if we should override transition animation. We only want to override if the Task
      * bounds is large enough for at least one split rule.
      */
+    @GuardedBy("mLock")
     private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
         if (ENABLE_SHELL_TRANSITIONS) {
             // TODO(b/207070762): cleanup with legacy app transition
             // Animation will be handled by WM Shell with Shell transition enabled.
             return;
         }
-        if (!taskContainer.isTaskBoundsInitialized()
-                || !taskContainer.isWindowingModeInitialized()) {
+        if (!taskContainer.isTaskBoundsInitialized()) {
             // We don't know about the Task bounds/windowingMode yet.
             return;
         }
 
-        // We only want to override if it supports split.
-        if (supportSplit(taskContainer)) {
+        // We only want to override if the TaskContainer may show split.
+        if (mayShowSplit(taskContainer)) {
             mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
         } else {
             mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
         }
     }
 
-    private boolean supportSplit(@NonNull TaskContainer taskContainer) {
+    /** Returns whether the given {@link TaskContainer} may show in split. */
+    // Suppress GuardedBy warning because lint asks to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
         // No split inside PIP.
         if (taskContainer.isInPictureInPicture()) {
             return false;
         }
+        // Always assume the TaskContainer if SplitAttributesCalculator is set
+        if (mSplitAttributesCalculator != null) {
+            return true;
+        }
         // Check if the parent container bounds can support any split rule.
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof SplitRule)) {
                 continue;
             }
-            if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
+            final SplitRule splitRule = (SplitRule) rule;
+            final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+                    taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
+            if (shouldShowSplit(splitAttributes)) {
                 return true;
             }
         }
@@ -700,14 +796,18 @@
     /**
      * Starts an activity to side of the launchingActivity with the provided split config.
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @GuardedBy("mLock")
     private void startActivityToSide(@NonNull WindowContainerTransaction wct,
             @NonNull Activity launchingActivity, @NonNull Intent intent,
             @Nullable Bundle options, @NonNull SplitRule sideRule,
-            @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
+            @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
+            boolean isPlaceholder) {
         try {
             mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
-                    isPlaceholder);
+                    splitAttributes, isPlaceholder);
         } catch (Exception e) {
             if (failureCallback != null) {
                 failureCallback.accept(e);
@@ -734,6 +834,10 @@
     }
 
     /** Whether the given new launched activity is in a split with a rule matched. */
+    // Suppress GuardedBy warning because lint asks to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
         final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(container);
@@ -827,8 +931,9 @@
         final TaskFragmentContainer primaryContainer = getContainerWithActivity(
                 primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
+        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
-                && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
+                && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -958,6 +1063,7 @@
      */
     @VisibleForTesting
     @Nullable
+    @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
         /*
@@ -1020,6 +1126,7 @@
     /**
      * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
      */
+    @GuardedBy("mLock")
     @Nullable
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@@ -1061,8 +1168,9 @@
         }
         final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
+        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
-                && (canReuseContainer(splitRule, splitContainer.getSplitRule())
+                && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
                 // TODO(b/231845476) we should always respect clearTop.
                 || !respectClearTop)
                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1101,12 +1209,14 @@
         return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
     }
 
+    @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
                 activityInTask, taskId);
     }
 
+    @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
@@ -1130,7 +1240,7 @@
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
         if (!mTaskContainers.contains(taskId)) {
-            mTaskContainers.put(taskId, new TaskContainer(taskId));
+            mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
@@ -1142,10 +1252,6 @@
                 Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
             }
         }
-        if (!taskContainer.isWindowingModeInitialized()) {
-            taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration()
-                    .windowConfiguration.getWindowingMode());
-        }
         updateAnimationOverride(taskContainer);
         return container;
     }
@@ -1154,12 +1260,16 @@
      * Creates and registers a new split with the provided containers and configuration. Finishes
      * existing secondary containers if found for the given primary container.
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     void registerSplit(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
-            @NonNull SplitRule splitRule) {
+            @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
         final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
-                secondaryContainer, splitRule);
+                secondaryContainer, splitRule, splitAttributes);
         // Remove container later to prevent pinning escaping toast showing in lock task mode.
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
@@ -1310,6 +1420,12 @@
             // Skip position update - one or both containers are finished.
             return;
         }
+        final TaskContainer taskContainer = splitContainer.getTaskContainer();
+        final SplitRule splitRule = splitContainer.getSplitRule();
+        final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
+        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+                taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
+        splitContainer.setSplitAttributes(splitAttributes);
         if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
             // Placeholder was finished, the positions will be updated when its container is emptied
             return;
@@ -1383,6 +1499,9 @@
         return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
     }
 
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @GuardedBy("mLock")
     boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
             @NonNull Activity activity, boolean isOnCreated) {
@@ -1409,18 +1528,20 @@
             return false;
         }
 
+        final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
                 placeholderRule.getPlaceholderIntent());
-        if (!shouldShowSideBySide(
-                mPresenter.getParentContainerBounds(activity), placeholderRule,
-                minDimensionsPair)) {
+        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
+                placeholderRule, minDimensionsPair);
+        if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
 
         // TODO(b/190433398): Handle failed request
         final Bundle options = getPlaceholderOptions(activity, isOnCreated);
         startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
-                placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
+                placeholderRule, splitAttributes, null /* failureCallback */,
+                true /* isPlaceholder */);
         return true;
     }
 
@@ -1445,6 +1566,9 @@
         return options.toBundle();
     }
 
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@@ -1457,11 +1581,10 @@
             // The placeholder should remain after it was first shown.
             return false;
         }
-
-        if (shouldShowSideBySide(splitContainer)) {
+        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+        if (SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
-
         mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
                 false /* shouldFinishDependent */);
         return true;
@@ -1471,6 +1594,7 @@
      * Returns the rule to launch a placeholder for the activity with the provided component name
      * if it is configured in the split config.
      */
+    @GuardedBy("mLock")
     private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof SplitPlaceholderRule)) {
@@ -1487,6 +1611,7 @@
     /**
      * Notifies listeners about changes to split states if necessary.
      */
+    @GuardedBy("mLock")
     private void updateCallbackIfNecessary() {
         if (mEmbeddingCallback == null) {
             return;
@@ -1508,6 +1633,7 @@
      * null, that indicates that the active split states are in an intermediate state and should
      * not be reported.
      */
+    @GuardedBy("mLock")
     @Nullable
     private List<SplitInfo> getActiveSplitStates() {
         List<SplitInfo> splitStates = new ArrayList<>();
@@ -1526,20 +1652,8 @@
                         .toActivityStack();
                 final ActivityStack secondaryContainer = container.getSecondaryContainer()
                         .toActivityStack();
-                final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
-                        ? new SplitAttributes.SplitType.RatioSplitType(
-                                container.getSplitRule().getSplitRatio())
-                        : new SplitAttributes.SplitType.ExpandContainersSplitType();
                 final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
-                        // Splits that are not showing side-by-side are reported as having 0 split
-                        // ratio, since by definition in the API the primary container occupies no
-                        // width of the split when covered by the secondary.
-                        // TODO(b/241042437): use v2 APIs for splitAttributes
-                        new SplitAttributes.Builder()
-                                .setSplitType(splitType)
-                                .setLayoutDirection(container.getSplitRule().getLayoutDirection())
-                                .build()
-                        );
+                        container.getSplitAttributes());
                 splitStates.add(splitState);
             }
         }
@@ -1577,6 +1691,7 @@
      * Returns a split rule for the provided pair of primary activity and secondary activity intent
      * if available.
      */
+    @GuardedBy("mLock")
     @Nullable
     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
             @NonNull Intent secondaryActivityIntent) {
@@ -1595,6 +1710,7 @@
     /**
      * Returns a split rule for the provided pair of primary and secondary activities if available.
      */
+    @GuardedBy("mLock")
     @Nullable
     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
             @NonNull Activity secondaryActivity) {
@@ -1669,6 +1785,7 @@
      * Returns {@code true} if an Activity with the provided component name should always be
      * expanded to occupy full task bounds. Such activity must not be put in a split.
      */
+    @GuardedBy("mLock")
     private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof ActivityRule)) {
@@ -1694,6 +1811,10 @@
      * 'sticky' and the placeholder was finished when fully overlapping the primary container.
      * @return {@code true} if the associated container should be retained (and not be finished).
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
             @NonNull TaskFragmentContainer associatedContainer) {
         SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
@@ -1712,7 +1833,7 @@
         }
         // Decide whether the associated container should be retained based on the current
         // presentation mode.
-        if (shouldShowSideBySide(splitContainer)) {
+        if (shouldShowSplit(splitContainer)) {
             return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
         } else {
             return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
@@ -1905,23 +2026,33 @@
      * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
      * there is any.
      */
-    private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
+    private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
+            @NonNull WindowMetrics parentWindowMetrics) {
         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
             return false;
         }
-        return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2);
+        return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+                parentWindowMetrics);
     }
 
     /** Whether the two rules have the same presentation. */
-    private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
-            @NonNull SplitPairRule rule2) {
+    @VisibleForTesting
+    static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+            @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
+        if (rule1.getTag() != null || rule2.getTag() != null) {
+            // Tag must be unique if it is set. We don't want to reuse the container if the rules
+            // have different tags because they can have different SplitAttributes later through
+            // SplitAttributesCalculator.
+            return Objects.equals(rule1.getTag(), rule2.getTag());
+        }
+        // If both rules don't have tag, compare all SplitRules' properties that may affect their
+        // SplitAttributes.
         // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
-        return rule1.getSplitRatio() == rule2.getSplitRatio()
-                && rule1.getLayoutDirection() == rule2.getLayoutDirection()
-                && rule1.getFinishPrimaryWithSecondary()
-                == rule2.getFinishPrimaryWithSecondary()
-                && rule1.getFinishSecondaryWithPrimary()
-                == rule2.getFinishSecondaryWithPrimary();
+        return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
+                && rule1.checkParentMetrics(parentWindowMetrics)
+                == rule2.checkParentMetrics(parentWindowMetrics)
+                && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
+                && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
     }
 
     /**
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 2ef8e4c..7960323 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -22,11 +22,11 @@
 import android.app.ActivityThread;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -42,9 +42,21 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
+import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams;
+import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -66,11 +78,25 @@
     })
     private @interface Position {}
 
+    private static final int CONTAINER_POSITION_LEFT = 0;
+    private static final int CONTAINER_POSITION_TOP = 1;
+    private static final int CONTAINER_POSITION_RIGHT = 2;
+    private static final int CONTAINER_POSITION_BOTTOM = 3;
+
+    @IntDef(value = {
+            CONTAINER_POSITION_LEFT,
+            CONTAINER_POSITION_TOP,
+            CONTAINER_POSITION_RIGHT,
+            CONTAINER_POSITION_BOTTOM,
+    })
+    private @interface ContainerPosition {}
+
     /**
      * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
      * Activity, Activity, Intent)}.
      * No need to expand the splitContainer because screen is big enough to
-     * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied.
+     * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is
+     * satisfied.
      */
     static final int RESULT_NOT_EXPANDED = 0;
     /**
@@ -78,7 +104,7 @@
      * Activity, Activity, Intent)}.
      * The splitContainer should be expanded. It is usually because minimum dimensions is not
      * satisfied.
-     * @see #shouldShowSideBySide(Rect, SplitRule, Pair)
+     * @see #shouldShowSplit(SplitAttributes)
      */
     static final int RESULT_EXPANDED = 1;
     /**
@@ -101,6 +127,12 @@
     })
     private @interface ResultCode {}
 
+    @VisibleForTesting
+    static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES =
+            new SplitAttributes.Builder()
+            .setSplitType(new ExpandContainersSplitType())
+            .build();
+
     private final SplitController mController;
 
     SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
@@ -129,14 +161,17 @@
      * @return The newly created secondary container.
      */
     @NonNull
+    @GuardedBy("mController.mLock")
     TaskFragmentContainer createNewSplitWithEmptySideContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
             @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
-        final Rect parentBounds = getParentContainerBounds(primaryActivity);
+        final TaskProperties taskProperties = getTaskProperties(primaryActivity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
                 primaryActivity, secondaryIntent);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+                minDimensionsPair);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
                 primaryActivity, primaryRectBounds, null);
 
@@ -144,8 +179,8 @@
         final int taskId = primaryContainer.getTaskId();
         final TaskFragmentContainer secondaryContainer = mController.newContainer(
                 secondaryIntent, primaryActivity, taskId);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
-                rule, primaryActivity, minDimensionsPair);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final int windowingMode = mController.getTaskContainer(taskId)
                 .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
         createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -154,9 +189,10 @@
 
         // Set adjacent to each other so that the containers below will be invisible.
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
 
-        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+                splitAttributes);
 
         return secondaryContainer;
     }
@@ -176,16 +212,18 @@
     void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
             @NonNull SplitPairRule rule) {
-        final Rect parentBounds = getParentContainerBounds(primaryActivity);
+        final TaskProperties taskProperties = getTaskProperties(primaryActivity);
         final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
                 secondaryActivity);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+                minDimensionsPair);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
                 primaryActivity, primaryRectBounds, null);
 
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity);
         TaskFragmentContainer containerToAvoid = primaryContainer;
@@ -200,9 +238,10 @@
 
         // Set adjacent to each other so that the containers below will be invisible.
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
 
-        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+                splitAttributes);
     }
 
     /**
@@ -244,16 +283,16 @@
      * @param rule              The split rule to be applied to the container.
      * @param isPlaceholder     Whether the launch is a placeholder.
      */
+    @GuardedBy("mController.mLock")
     void startActivityToSide(@NonNull WindowContainerTransaction wct,
             @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
-            @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
-        final Rect parentBounds = getParentContainerBounds(launchingActivity);
-        final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
-                launchingActivity, activityIntent);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                launchingActivity, minDimensionsPair);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                launchingActivity, minDimensionsPair);
+            @Nullable Bundle activityOptions, @NonNull SplitRule rule,
+            @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
+        final TaskProperties taskProperties = getTaskProperties(launchingActivity);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
 
         TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
                 launchingActivity);
@@ -268,7 +307,7 @@
         final int windowingMode = mController.getTaskContainer(taskId)
                 .getWindowingModeForSplitTaskFragment(primaryRectBounds);
         mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
-                rule);
+                rule, splitAttributes);
         startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
                 launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
                 activityIntent, activityOptions, rule, windowingMode);
@@ -284,22 +323,24 @@
      * @param updatedContainer The task fragment that was updated and caused this split update.
      * @param wct WindowContainerTransaction that this update should be performed with.
      */
+    @GuardedBy("mController.mLock")
     void updateSplitContainer(@NonNull SplitContainer splitContainer,
             @NonNull TaskFragmentContainer updatedContainer,
             @NonNull WindowContainerTransaction wct) {
-        // Getting the parent bounds using the updated container - it will have the recent value.
-        final Rect parentBounds = getParentContainerBounds(updatedContainer);
+        // Getting the parent configuration using the updated container - it will have the recent
+        // value.
         final SplitRule rule = splitContainer.getSplitRule();
         final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
         final Activity activity = primaryContainer.getTopNonFinishingActivity();
         if (activity == null) {
             return;
         }
-        final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                activity, minDimensionsPair);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                activity, minDimensionsPair);
+        final TaskProperties taskProperties = getTaskProperties(updatedContainer);
+        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
         // Whether the placeholder is becoming side-by-side with the primary from fullscreen.
         final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
@@ -311,7 +352,7 @@
         resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
         resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
         if (isPlaceholderBecomingSplit) {
             // When placeholder is shown in split, we should keep the focus on the primary.
             wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -323,14 +364,14 @@
         updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
     }
 
+    @GuardedBy("mController.mLock")
     private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer primaryContainer,
             @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
-            @NonNull Pair<Size, Size> minDimensionsPair) {
-        final Rect parentBounds = getParentContainerBounds(primaryContainer);
+            @NonNull SplitAttributes splitAttributes) {
         // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
         // secondaryContainer could not be finished.
-        if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) {
+        if (!shouldShowSplit(splitAttributes)) {
             setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
                     null /* secondary */, null /* splitRule */);
         } else {
@@ -416,8 +457,9 @@
      * Expands the split container if the current split bounds are smaller than the Activity or
      * Intent that is added to the container.
      *
-     * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)}
-     * and if {@link android.window.TaskFragmentInfo} has reported to the client side.
+     * @return the {@link ResultCode} based on
+     * {@link #shouldShowSplit(SplitAttributes)} and if
+     * {@link android.window.TaskFragmentInfo} has reported to the client side.
      */
     @ResultCode
     int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
@@ -427,7 +469,6 @@
             throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
                     + " non-null.");
         }
-        final Rect taskBounds = getParentContainerBounds(primaryActivity);
         final Pair<Size, Size> minDimensionsPair;
         if (secondaryActivity != null) {
             minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
@@ -436,7 +477,12 @@
                     secondaryIntent);
         }
         // Expand the splitContainer if minimum dimensions are not satisfied.
-        if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
+        final TaskContainer taskContainer = splitContainer.getTaskContainer();
+        final SplitAttributes splitAttributes = sanitizeSplitAttributes(
+                taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
+                minDimensionsPair);
+        splitContainer.setSplitAttributes(splitAttributes);
+        if (!shouldShowSplit(splitAttributes)) {
             // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
             // bounds. Return failure to create a new SplitContainer which fills task bounds.
             if (splitContainer.getPrimaryContainer().getInfo() == null
@@ -450,36 +496,63 @@
         return RESULT_NOT_EXPANDED;
     }
 
-    static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
-        return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
+    static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
+        return shouldShowSplit(splitContainer.getSplitAttributes());
     }
 
-    static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
-        final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
-
-        return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
-                splitContainer.getMinDimensionsPair());
+    static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
+        return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
     }
 
-    static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule,
+    @GuardedBy("mController.mLock")
+    @NonNull
+    SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
+            @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+        final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
+        final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
+        final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
+        if (calculator == null) {
+            if (!isDefaultMinSizeSatisfied) {
+                return EXPAND_CONTAINERS_ATTRIBUTES;
+            }
+            return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
+                    minDimensionsPair);
+        }
+        final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent
+                .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+                        taskConfiguration.windowConfiguration);
+        final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
+                taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
+                isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
+        final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
+        return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
+    }
+
+    /**
+     * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't
+     * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns
+     * the passed {@link SplitAttributes}.
+     */
+    @NonNull
+    private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
-        // TODO(b/190433398): Supply correct insets.
-        final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
-                new WindowInsets(new Rect()));
-        // Don't show side by side if bounds is not qualified.
-        if (!rule.checkParentMetrics(parentMetrics)) {
-            return false;
-        }
-        final float splitRatio = rule.getSplitRatio();
-        // We only care the size of the bounds regardless of its position.
-        final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */);
-        final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */);
-
         if (minDimensionsPair == null) {
-            return true;
+            return splitAttributes;
         }
-        return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
-                && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second);
+        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
+                foldingFeature);
+        final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes,
+                foldingFeature);
+        if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
+                || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) {
+            return EXPAND_CONTAINERS_ATTRIBUTES;
+        }
+        return splitAttributes;
     }
 
     @NonNull
@@ -541,20 +614,25 @@
 
     @VisibleForTesting
     @NonNull
-    static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
-            @NonNull SplitRule rule, @NonNull Activity primaryActivity,
-            @Nullable Pair<Size, Size> minDimensionsPair) {
-        if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) {
+    Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes) {
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
+                foldingFeature);
+        final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
+                .setSplitType(splitType)
+                .setLayoutDirection(splitAttributes.getLayoutDirection())
+                .build();
+        if (!shouldShowSplit(computedSplitAttributes)) {
             return new Rect();
         }
-        final boolean isLtr = isLtr(primaryActivity, rule);
-        final float splitRatio = rule.getSplitRatio();
-
         switch (position) {
             case POSITION_START:
-                return getPrimaryBounds(parentBounds, splitRatio, isLtr);
+                return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
             case POSITION_END:
-                return getSecondaryBounds(parentBounds, splitRatio, isLtr);
+                return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
+                        foldingFeature);
             case POSITION_FILL:
             default:
                 return new Rect();
@@ -562,74 +640,303 @@
     }
 
     @NonNull
-    private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio,
-            boolean isLtr) {
-        return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
-                : getRightContainerBounds(parentBounds, 1 - splitRatio);
-    }
-
-    @NonNull
-    private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio,
-            boolean isLtr) {
-        return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
-                : getLeftContainerBounds(parentBounds, 1 - splitRatio);
-    }
-
-    private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
-        return new Rect(
-                parentBounds.left,
-                parentBounds.top,
-                (int) (parentBounds.left + parentBounds.width() * splitRatio),
-                parentBounds.bottom);
-    }
-
-    private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
-        return new Rect(
-                (int) (parentBounds.left + parentBounds.width() * splitRatio),
-                parentBounds.top,
-                parentBounds.right,
-                parentBounds.bottom);
-    }
-
-    /**
-     * Checks if a split with the provided rule should be displays in left-to-right layout
-     * direction, either always or with the current configuration.
-     */
-    private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
-        switch (rule.getLayoutDirection()) {
-            case LayoutDirection.LOCALE:
-                return context.getResources().getConfiguration().getLayoutDirection()
+    private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        if (!shouldShowSplit(splitAttributes)) {
+            return new Rect();
+        }
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+                return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+                return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.LOCALE: {
+                final boolean isLtr = taskConfiguration.getLayoutDirection()
                         == View.LAYOUT_DIRECTION_LTR;
-            case LayoutDirection.RTL:
-                return false;
-            case LayoutDirection.LTR:
+                return isLtr
+                        ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
+                        : getRightContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+                return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+                return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
             default:
-                return true;
+                throw new IllegalArgumentException("Unknown layout direction:"
+                        + splitAttributes.getLayoutDirection());
         }
     }
 
     @NonNull
-    static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
-        return container.getTaskContainer().getTaskBounds();
+    private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        if (!shouldShowSplit(splitAttributes)) {
+            return new Rect();
+        }
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+                return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+                return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.LOCALE: {
+                final boolean isLtr = taskConfiguration.getLayoutDirection()
+                        == View.LAYOUT_DIRECTION_LTR;
+                return isLtr
+                        ? getRightContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature)
+                        : getLeftContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+                return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+                return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            default:
+                throw new IllegalArgumentException("Unknown layout direction:"
+                        + splitAttributes.getLayoutDirection());
+        }
     }
 
     @NonNull
-    Rect getParentContainerBounds(@NonNull Activity activity) {
-        final TaskFragmentContainer container = mController.getContainerWithActivity(activity);
-        if (container != null) {
-            return getParentContainerBounds(container);
-        }
-        // Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
-        return getNonEmbeddedActivityBounds(activity);
+    private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_LEFT, foldingFeature);
+        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
+    }
+
+    @NonNull
+    private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_RIGHT, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom);
+    }
+
+    @NonNull
+    private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_TOP, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom);
+    }
+
+    @NonNull
+    private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_BOTTOM, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom);
     }
 
     /**
-     * Obtains the bounds from a non-embedded Activity.
-     * <p>
-     * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most
-     * cases unless we want to obtain task bounds before
-     * {@link TaskContainer#isTaskBoundsInitialized()}.
+     * Computes the boundary position between the primary and the secondary containers for the given
+     * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states.
+     * <ol>
+     *     <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom
+     *       container, which is {@link Rect#bottom} of the top container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top
+     *       container, which is {@link Rect#top} of the bottom container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right
+     *       container, which is {@link Rect#right} of the left container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom
+     *       container, which is {@link Rect#left} of the right container bounds.</li>
+     * </ol>
+     *
+     * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature)
      */
+    private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @ContainerPosition int position,
+            @Nullable FoldingFeature foldingFeature) {
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        final int startPoint = shouldSplitHorizontally(splitAttributes)
+                ? parentBounds.top
+                : parentBounds.left;
+        final int dimen = shouldSplitHorizontally(splitAttributes)
+                ? parentBounds.height()
+                : parentBounds.width();
+        final SplitType splitType = splitAttributes.getSplitType();
+        if (splitType instanceof RatioSplitType) {
+            final RatioSplitType splitRatio = (RatioSplitType) splitType;
+            return (int) (startPoint + dimen * splitRatio.getRatio());
+        }
+        // At this point, SplitType must be a HingeSplitType and foldingFeature must be
+        // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier.
+        Objects.requireNonNull(foldingFeature);
+        if (!(splitType instanceof HingeSplitType)) {
+            throw new IllegalArgumentException("Unknown splitType:" + splitType);
+        }
+        final Rect hingeArea = foldingFeature.getBounds();
+        switch (position) {
+            case CONTAINER_POSITION_LEFT:
+                return hingeArea.left;
+            case CONTAINER_POSITION_TOP:
+                return hingeArea.top;
+            case CONTAINER_POSITION_RIGHT:
+                return hingeArea.right;
+            case CONTAINER_POSITION_BOTTOM:
+                return hingeArea.bottom;
+            default:
+                throw new IllegalArgumentException("Unknown position:" + position);
+        }
+    }
+
+    @Nullable
+    private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
+        final int displayId = taskProperties.getDisplayId();
+        final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
+                .windowConfiguration;
+        final WindowLayoutInfo info = mController.mWindowLayoutComponent
+                .getCurrentWindowLayoutInfo(displayId, windowConfiguration);
+        final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
+        if (displayFeatures.isEmpty()) {
+            return null;
+        }
+        final List<FoldingFeature> foldingFeatures = new ArrayList<>();
+        for (DisplayFeature displayFeature : displayFeatures) {
+            if (displayFeature instanceof FoldingFeature) {
+                foldingFeatures.add((FoldingFeature) displayFeature);
+            }
+        }
+        // TODO(b/240219484): Support device with multiple hinges.
+        if (foldingFeatures.size() != 1) {
+            return null;
+        }
+        return foldingFeatures.get(0);
+    }
+
+    /**
+     * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns
+     * {@code false} if this {@link SplitAttributes} splits the task vertically.
+     */
+    private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) {
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and
+     * window state.
+     * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed
+     * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is
+     * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or
+     * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier.
+     * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks
+     * the current device and window states to determine whether the split container should split
+     * by hinge or use {@link HingeSplitType#getFallbackSplitType}.
+     */
+    private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
+            @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
+        final int layoutDirection = splitAttributes.getLayoutDirection();
+        final SplitType splitType = splitAttributes.getSplitType();
+        if (splitType instanceof ExpandContainersSplitType) {
+            return splitType;
+        } else if (splitType instanceof RatioSplitType) {
+            final RatioSplitType splitRatio = (RatioSplitType) splitType;
+            // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
+            // computation have the same direction, which is from (top, left) to (bottom, right).
+            final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
+            switch (layoutDirection) {
+                case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
+                case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+                    return splitType;
+                case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
+                case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+                    return reversedSplitType;
+                case LayoutDirection.LOCALE: {
+                    boolean isLtr = taskConfiguration.getLayoutDirection()
+                            == View.LAYOUT_DIRECTION_LTR;
+                    return isLtr ? splitType : reversedSplitType;
+                }
+            }
+        } else if (splitType instanceof HingeSplitType) {
+            final HingeSplitType hinge = (HingeSplitType) splitType;
+            @WindowingMode
+            final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode();
+            return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode)
+                    ? hinge : hinge.getFallbackSplitType();
+        }
+        throw new IllegalArgumentException("Unknown SplitType:" + splitType);
+    }
+
+    private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes,
+            @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) {
+        // Only HingeSplitType may split the task bounds by hinge.
+        if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) {
+            return false;
+        }
+        // Device is not foldable, so there's no hinge to match.
+        if (foldingFeature == null) {
+            return false;
+        }
+        // The task is in multi-window mode. Match hinge doesn't make sense because current task
+        // bounds may not fit display bounds.
+        if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) {
+            return false;
+        }
+        // Return true if how the split attributes split the task bounds matches the orientation of
+        // folding area orientation.
+        return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature);
+    }
+
+    private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) {
+        final Rect bounds = foldingFeature.getBounds();
+        return bounds.width() > bounds.height();
+    }
+
+    @NonNull
+    static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
+        return container.getTaskContainer().getTaskProperties();
+    }
+
+    @NonNull
+    TaskProperties getTaskProperties(@NonNull Activity activity) {
+        final TaskContainer taskContainer = mController.getTaskContainer(
+                mController.getTaskId(activity));
+        if (taskContainer != null) {
+            return taskContainer.getTaskProperties();
+        }
+        // Use a copy of configuration because activity's configuration may be updated later,
+        // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+        // updated. An example is Activity is going to be in split.
+        return new TaskProperties(activity.getDisplayId(),
+                new Configuration(activity.getResources().getConfiguration()));
+    }
+
+    @NonNull
+    WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
+        return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
+    }
+
+    @NonNull
+    private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+        // TODO(b/190433398): Supply correct insets.
+        return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
+    }
+
+    /** Obtains the bounds from a non-embedded Activity. */
     @NonNull
     static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
         final WindowConfiguration windowConfiguration =
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 b563677..91573ff 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -24,10 +24,13 @@
 import android.app.Activity;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -42,13 +45,10 @@
     /** The unique task id. */
     private final int mTaskId;
 
+    // TODO(b/240219484): consolidate to mConfiguration
     /** Available window bounds of this Task. */
     private final Rect mTaskBounds = new Rect();
 
-    /** Windowing mode of this Task. */
-    @WindowingMode
-    private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
-
     /** Active TaskFragments in this Task. */
     @NonNull
     final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -57,24 +57,56 @@
     @NonNull
     final List<SplitContainer> mSplitContainers = new ArrayList<>();
 
+    @NonNull
+    private final Configuration mConfiguration;
+
+    private int mDisplayId;
+
+    private boolean mIsVisible;
+
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
-     * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event
-     * for them.
+     * the organizer receives
+     * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)}
+     * event for them.
      */
     final Set<IBinder> mFinishedContainer = new ArraySet<>();
 
-    TaskContainer(int taskId) {
+    /**
+     * The {@link TaskContainer} constructor
+     *
+     * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
+     *               {@code activityInTask}.
+     * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
+     *                       initialize the {@link TaskContainer} properties.
+     *
+     */
+    TaskContainer(int taskId, @NonNull Activity activityInTask) {
         if (taskId == INVALID_TASK_ID) {
             throw new IllegalArgumentException("Invalid Task id");
         }
         mTaskId = taskId;
+        // Make a copy in case the activity's config is updated, and updates the TaskContainer's
+        // config unexpectedly.
+        mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
+        mDisplayId = activityInTask.getDisplayId();
+        // Note that it is always called when there's a new Activity is started, which implies
+        // the host task is visible.
+        mIsVisible = true;
     }
 
     int getTaskId() {
         return mTaskId;
     }
 
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    boolean isVisible() {
+        return mIsVisible;
+    }
+
     @NonNull
     Rect getTaskBounds() {
         return mTaskBounds;
@@ -94,13 +126,21 @@
         return !mTaskBounds.isEmpty();
     }
 
-    void setWindowingMode(int windowingMode) {
-        mWindowingMode = windowingMode;
+    @NonNull
+    Configuration getConfiguration() {
+        // Make a copy in case the config is updated unexpectedly.
+        return new Configuration(mConfiguration);
     }
 
-    /** Whether the Task windowing mode has been initialized. */
-    boolean isWindowingModeInitialized() {
-        return mWindowingMode != WINDOWING_MODE_UNDEFINED;
+    @NonNull
+    TaskProperties getTaskProperties() {
+        return new TaskProperties(mDisplayId, mConfiguration);
+    }
+
+    void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+        mConfiguration.setTo(info.getConfiguration());
+        mDisplayId = info.getDisplayId();
+        mIsVisible = info.isVisibleRequested();
     }
 
     /**
@@ -123,13 +163,20 @@
         // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
         // Task windowing mode if the Task is in multi window.
         // TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
-        return WindowConfiguration.inMultiWindowMode(mWindowingMode)
-                ? mWindowingMode
-                : WINDOWING_MODE_MULTI_WINDOW;
+        return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW;
     }
 
     boolean isInPictureInPicture() {
-        return mWindowingMode == WINDOWING_MODE_PINNED;
+        return getWindowingMode() == WINDOWING_MODE_PINNED;
+    }
+
+    boolean isInMultiWindow() {
+        return WindowConfiguration.inMultiWindowMode(getWindowingMode());
+    }
+
+    @WindowingMode
+    private int getWindowingMode() {
+        return getConfiguration().windowConfiguration.getWindowingMode();
     }
 
     /** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -173,4 +220,28 @@
     int indexOf(@NonNull TaskFragmentContainer child) {
         return mContainers.indexOf(child);
     }
+
+    /**
+     * A wrapper class which contains the display ID and {@link Configuration} of a
+     * {@link TaskContainer}
+     */
+    static final class TaskProperties {
+        private final int mDisplayId;
+        @NonNull
+        private final Configuration mConfiguration;
+
+        TaskProperties(int displayId, @NonNull Configuration configuration) {
+            mDisplayId = displayId;
+            mConfiguration = configuration;
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        @NonNull
+        Configuration getConfiguration() {
+            return mConfiguration;
+        }
+    }
 }
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 626e0d9..18712ae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -432,7 +432,7 @@
                     // In case we have requested to reparent the activity to another container (as
                     // pendingAppeared), we don't want to finish it with this container.
                     && mController.getContainerWithActivity(activity) == this) {
-                activity.finish();
+                wct.finishActivity(activity.getActivityToken());
             }
         }
 
@@ -457,7 +457,7 @@
                     || controller.shouldRetainAssociatedActivity(this, activity)) {
                 continue;
             }
-            activity.finish();
+            wct.finishActivity(activity.getActivityToken());
         }
         mActivitiesToFinishOnExit.clear();
     }
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 f24401f..c76f568 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -47,6 +47,7 @@
 import androidx.window.util.DataProducer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -68,6 +69,8 @@
 
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
+    private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
+
     private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
             new ArrayMap<>();
 
@@ -80,6 +83,11 @@
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
+    /** Registers to listen to {@link CommonFoldingFeature} changes */
+    public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
+        mFoldingFeatureProducer.addDataChangedCallback(consumer);
+    }
+
     /**
      * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
      *
@@ -186,6 +194,8 @@
     }
 
     private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+        mLastReportedFoldingFeatures.clear();
+        mLastReportedFoldingFeatures.addAll(storedFeatures);
         for (Context context : getContextsListeningForLayoutChanges()) {
             // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
             Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
@@ -207,6 +217,27 @@
     }
 
     /**
+     * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}.
+     *
+     * @return current {@link WindowLayoutInfo} on the default display. Returns
+     *   empty {@link WindowLayoutInfo} on secondary displays.
+     */
+    @NonNull
+    public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId,
+            @NonNull WindowConfiguration windowConfiguration) {
+        return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures);
+    }
+
+    /** @see #getWindowLayoutInfo(Context, List)  */
+    private WindowLayoutInfo getWindowLayoutInfo(int displayId,
+            @NonNull WindowConfiguration windowConfiguration,
+            List<CommonFoldingFeature> storedFeatures) {
+        List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration,
+                storedFeatures);
+        return new WindowLayoutInfo(displayFeatureList);
+    }
+
+    /**
      * Translate from the {@link CommonFoldingFeature} to
      * {@link DisplayFeature} for a given {@link Activity}. If a
      * {@link CommonFoldingFeature} is not valid then it will be omitted.
@@ -225,12 +256,23 @@
      */
     private List<DisplayFeature> getDisplayFeatures(
             @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
-        List<DisplayFeature> features = new ArrayList<>();
         if (!shouldReportDisplayFeatures(context)) {
+            return Collections.emptyList();
+        }
+        return getDisplayFeatures(context.getDisplayId(),
+                context.getResources().getConfiguration().windowConfiguration,
+                storedFeatures);
+    }
+
+    /** @see #getDisplayFeatures(Context, List) */
+    private List<DisplayFeature> getDisplayFeatures(int displayId,
+            @NonNull WindowConfiguration windowConfiguration,
+            List<CommonFoldingFeature> storedFeatures) {
+        List<DisplayFeature> features = new ArrayList<>();
+        if (displayId != DEFAULT_DISPLAY) {
             return features;
         }
 
-        int displayId = context.getDisplay().getDisplayId();
         for (CommonFoldingFeature baseFeature : storedFeatures) {
             Integer state = convertToExtensionState(baseFeature.getState());
             if (state == null) {
@@ -238,7 +280,7 @@
             }
             Rect featureRect = baseFeature.getRect();
             rotateRectToDisplayRotation(displayId, featureRect);
-            transformToWindowSpaceRect(context, featureRect);
+            transformToWindowSpaceRect(windowConfiguration, featureRect);
 
             if (!isZero(featureRect)) {
                 // TODO(b/228641877): Remove guarding when fixed.
@@ -263,6 +305,8 @@
             windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
                     context.getActivityToken());
         } else {
+            // TODO(b/242674941): use task windowing mode for window context that associates with
+            //  activity.
             windowingMode = context.getResources().getConfiguration().windowConfiguration
                     .getWindowingMode();
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 31bf963..9e2611f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -21,6 +21,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
@@ -89,13 +90,21 @@
     /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
     public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
             Rect inOutRect) {
-        Rect windowRect = getWindowBounds(context);
-        if (!Rect.intersects(inOutRect, windowRect)) {
+        transformToWindowSpaceRect(getWindowBounds(context), inOutRect);
+    }
+
+    /** @see ExtensionHelper#transformToWindowSpaceRect(Context, Rect) */
+    public static void transformToWindowSpaceRect(@NonNull WindowConfiguration windowConfiguration,
+            Rect inOutRect) {
+        transformToWindowSpaceRect(windowConfiguration.getBounds(), inOutRect);
+    }
+
+    private static void transformToWindowSpaceRect(@NonNull Rect bounds, @NonNull Rect inOutRect) {
+        if (!inOutRect.intersect(bounds)) {
             inOutRect.setEmpty();
             return;
         }
-        inOutRect.intersect(windowRect);
-        inOutRect.offset(-windowRect.left, -windowRect.top);
+        inOutRect.offset(-bounds.left, -bounds.top);
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index effc1a3..40f7a27 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -16,9 +16,12 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
 import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
 
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
@@ -26,32 +29,68 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Pair;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerToken;
 
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 public class EmbeddingTestUtils {
     static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
     static final int TASK_ID = 10;
-    static final float SPLIT_RATIO = 0.5f;
+    static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually();
+    static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build();
+    static final String TEST_TAG = "test";
     /** Default finish behavior in Jetpack. */
     static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
     static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
+    private static final float SPLIT_RATIO = 0.5f;
 
     private EmbeddingTestUtils() {}
 
     /** Gets the bounds of a TaskFragment that is in split. */
     static Rect getSplitBounds(boolean isPrimary) {
-        final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
+        return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */);
+    }
+
+    /** Gets the bounds of a TaskFragment that is in split. */
+    static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) {
+        final int dimension = (int) (
+                (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width())
+                        * SPLIT_RATIO);
+        if (shouldSplitHorizontally) {
+            return isPrimary
+                    ? new Rect(
+                            TASK_BOUNDS.left,
+                            TASK_BOUNDS.top,
+                            TASK_BOUNDS.right,
+                            TASK_BOUNDS.top + dimension)
+                    : new Rect(
+                            TASK_BOUNDS.left,
+                            TASK_BOUNDS.top + dimension,
+                            TASK_BOUNDS.right,
+                            TASK_BOUNDS.bottom);
+        }
         return isPrimary
-                ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
-                TASK_BOUNDS.bottom)
+                ? new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.left + dimension,
+                        TASK_BOUNDS.bottom)
                 : new Rect(
-                        TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
+                        TASK_BOUNDS.left + dimension,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right,
                         TASK_BOUNDS.bottom);
     }
 
@@ -69,10 +108,15 @@
                 activityPair -> false,
                 targetPair::equals,
                 w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(
+                        new SplitAttributes.Builder()
+                                .setSplitType(SPLIT_TYPE)
+                                .build()
+                )
                 .setShouldClearTop(clearTop)
                 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
                 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setTag(TEST_TAG)
                 .build();
     }
 
@@ -101,10 +145,15 @@
                 targetPair::equals,
                 activityIntentPair -> false,
                 w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(
+                        new SplitAttributes.Builder()
+                                .setSplitType(SPLIT_TYPE)
+                                .build()
+                )
                 .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
                 .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
                 .setShouldClearTop(clearTop)
+                .setTag(TEST_TAG)
                 .build();
     }
 
@@ -130,4 +179,29 @@
                 primaryBounds.width() + 1, primaryBounds.height() + 1);
         return aInfo;
     }
+
+    static TaskContainer createTestTaskContainer() {
+        Resources resources = mock(Resources.class);
+        doReturn(new Configuration()).when(resources).getConfiguration();
+        Activity activity = mock(Activity.class);
+        doReturn(resources).when(activity).getResources();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+
+        return new TaskContainer(TASK_ID, activity);
+    }
+
+    static WindowLayoutInfo createWindowLayoutInfo() {
+        final FoldingFeature foldingFeature = new FoldingFeature(
+                new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5
+                        ),
+                FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_HALF_OPENED);
+        final List<DisplayFeature> displayFeatures = new ArrayList<>();
+        displayFeatures.add(foldingFeature);
+        return new WindowLayoutInfo(displayFeatures);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 58a627b..957a248 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -26,12 +27,14 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentTransaction;
@@ -65,7 +68,10 @@
     private WindowContainerTransaction mTransaction;
     @Mock
     private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
+    @Mock
     private SplitController mSplitController;
+    @Mock
+    private Handler mHandler;
     private JetpackTaskFragmentOrganizer mOrganizer;
 
     @Before
@@ -73,9 +79,8 @@
         MockitoAnnotations.initMocks(this);
         mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
         mOrganizer.registerOrganizer();
-        mSplitController = new SplitController();
         spyOn(mOrganizer);
-        spyOn(mSplitController);
+        doReturn(mHandler).when(mSplitController).getHandler();
     }
 
     @Test
@@ -113,7 +118,7 @@
 
     @Test
     public void testExpandTaskFragment() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mSplitController);
         final TaskFragmentInfo info = createMockInfo(container);
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 58870a6..25d0347 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
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.START_CANCELED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -27,15 +28,20 @@
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
@@ -73,14 +79,19 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -116,6 +127,8 @@
     private WindowContainerTransaction mTransaction;
     @Mock
     private Handler mHandler;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
 
     private SplitController mSplitController;
     private SplitPresenter mSplitPresenter;
@@ -123,7 +136,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mSplitController = new SplitController();
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        mSplitController = new SplitController(mWindowLayoutComponent);
         mSplitPresenter = mSplitController.mPresenter;
         spyOn(mSplitController);
         spyOn(mSplitPresenter);
@@ -138,7 +153,7 @@
 
     @Test
     public void testGetTopActiveContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         // tf1 has no running activity so is not active.
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mSplitController);
@@ -192,12 +207,13 @@
 
         verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
         verify(mSplitController).removeContainer(tf);
-        verify(mActivity, never()).finish();
+        verify(mTransaction, never()).finishActivity(any());
     }
 
     @Test
     public void testOnTaskFragmentAppearEmptyTimeout() {
         final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
+        doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
         mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
 
         verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
@@ -268,6 +284,8 @@
         final SplitContainer splitContainer = mock(SplitContainer.class);
         doReturn(tf).when(splitContainer).getPrimaryContainer();
         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);
@@ -298,7 +316,7 @@
 
         // Verify if the top active split is updated if both of its containers are not finished.
         doReturn(false).when(mSplitController)
-                        .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
+                .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -308,7 +326,7 @@
     @Test
     public void testOnStartActivityResultError() {
         final Intent intent = new Intent();
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 intent, taskContainer, mSplitController);
         final SplitController.ActivityStartMonitor monitor =
@@ -608,7 +626,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -624,7 +642,7 @@
 
         assertFalse(result);
         verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
-                anyBoolean());
+                any(), anyBoolean());
     }
 
     @Test
@@ -641,7 +659,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -656,7 +674,7 @@
 
         assertFalse(result);
         verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
-                anyBoolean());
+                any(), anyBoolean());
     }
 
     @Test
@@ -674,7 +692,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -693,14 +711,15 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                splitRule);
+                splitRule,
+                SPLIT_ATTRIBUTES);
         clearInvocations(mSplitController);
         final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
-        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -720,7 +739,8 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                splitRule);
+                splitRule,
+                SPLIT_ATTRIBUTES);
         final Activity launchedActivity = createMockActivity();
         primaryContainer.addPendingAppearedActivity(launchedActivity);
 
@@ -741,7 +761,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
-        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -778,7 +798,8 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                placeholderRule);
+                placeholderRule,
+                SPLIT_ATTRIBUTES);
         final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
@@ -983,9 +1004,9 @@
         assertTrue(primaryContainer.isFinished());
         assertTrue(secondaryContainer0.isFinished());
         assertTrue(secondaryContainer1.isFinished());
-        verify(mActivity).finish();
-        verify(secondaryActivity0).finish();
-        verify(secondaryActivity1).finish();
+        verify(mTransaction).finishActivity(mActivity.getActivityToken());
+        verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
+        verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
         assertTrue(taskContainer.mContainers.isEmpty());
         assertTrue(taskContainer.mSplitContainers.isEmpty());
     }
@@ -1038,15 +1059,16 @@
     @Test
     public void testOnTransactionReady_taskFragmentParentInfoChanged() {
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
-        final Configuration taskConfig = new Configuration();
+        final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
+                DEFAULT_DISPLAY, true);
         transaction.addChange(new TaskFragmentTransaction.Change(
                 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                 .setTaskId(TASK_ID)
-                .setTaskConfiguration(taskConfig));
+                .setTaskFragmentParentInfo(parentInfo));
         mSplitController.onTransactionReady(transaction);
 
         verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
-                eq(taskConfig));
+                eq(parentInfo));
         verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
                 anyInt(), anyBoolean());
     }
@@ -1088,6 +1110,47 @@
                 anyInt(), anyBoolean());
     }
 
+    @Test
+    public void testHasSamePresentation() {
+        SplitPairRule splitRule1 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+        SplitPairRule splitRule2 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+
+        assertTrue("Rules must have same presentation if tags are null and has same properties.",
+                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                        new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+        splitRule2 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .setTag(TEST_TAG)
+                .build();
+
+        assertFalse("Rules must have different presentations if tags are not equal regardless"
+                        + "of other properties",
+                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                        new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
@@ -1097,6 +1160,7 @@
         doReturn(activity).when(mSplitController).getActivity(activityToken);
         doReturn(TASK_ID).when(activity).getTaskId();
         doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
         return activity;
     }
 
@@ -1135,7 +1199,7 @@
     private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
         final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
                 primaryActivity::equals, i -> false, w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .build();
         mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
     }
@@ -1188,7 +1252,8 @@
                 primaryContainer,
                 primaryContainer.getTopNonFinishingActivity(),
                 secondaryContainer,
-                rule);
+                rule,
+                SPLIT_ATTRIBUTES);
 
         // We need to set those in case we are not respecting clear top.
         // TODO(b/231845476) we should always respect clearTop.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 25f0e25..6dae0a1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -16,23 +16,28 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
+import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
-import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -66,6 +71,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,6 +80,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+
 /**
  * Test class for {@link SplitPresenter}.
  *
@@ -94,13 +103,17 @@
     private TaskFragmentInfo mTaskFragmentInfo;
     @Mock
     private WindowContainerTransaction mTransaction;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
     private SplitController mController;
     private SplitPresenter mPresenter;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new SplitController();
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        mController = new SplitController(mWindowLayoutComponent);
         mPresenter = mController.mPresenter;
         spyOn(mController);
         spyOn(mPresenter);
@@ -162,59 +175,263 @@
 
     @Test
     public void testShouldShowSideBySide() {
-        Activity secondaryActivity = createMockActivity();
-        final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+        assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES));
 
-        assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule));
+        final SplitAttributes expandContainers = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+                .build();
 
-        // Set minDimensions of primary container to larger than primary bounds.
-        final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
-        Pair<Size, Size> minDimensionsPair = new Pair<>(
-                new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
-
-        assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair));
+        assertFalse(SplitPresenter.shouldShowSplit(expandContainers));
     }
 
     @Test
-    public void testGetBoundsForPosition() {
-        Activity secondaryActivity = createMockActivity();
-        final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
-        final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
-        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
+    public void testGetBoundsForPosition_expandContainers() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+                .build();
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_splitVertically() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                false /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                false /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
 
         assertEquals("Primary bounds must be reported.",
                 primaryBounds,
-                getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
 
         assertEquals("Secondary bounds must be reported.",
                 secondaryBounds,
-                getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
         assertEquals("Task bounds must be reported.",
                 new Rect(),
-                getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
 
-        Pair<Size, Size> minDimensionsPair = new Pair<>(
-                new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT)
+                .build();
 
-        assertEquals("Fullscreen bounds must be reported because of min dimensions.",
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
                 new Rect(),
-                getBoundsForPosition(POSITION_START, TASK_BOUNDS,
-                        splitRule, mActivity, minDimensionsPair));
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+                .build();
+        // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE.
+        taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_splitHorizontally() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                true /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                true /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+                .build();
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
+                .build();
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_useHingeFallback() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                false /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                false /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        SplitAttributes.SplitType.RatioSplitType.splitEqually()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
+
+        // There's no hinge on the device. Use fallback SplitType.
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        // Hinge is reported, but the host task is in multi-window mode. Still use fallback
+        // splitType.
+        doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        taskProperties.getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        // Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match
+        // folding area orientation. Still use fallback splitType.
+        doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        taskProperties.getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_fallbackToExpandContainers() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        new SplitAttributes.SplitType.ExpandContainersSplitType()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_useHingeSplitType() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        new SplitAttributes.SplitType.ExpandContainersSplitType()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+                .build();
+        final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo();
+        doReturn(windowLayoutInfo).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds();
+        final Rect primaryBounds = new Rect(
+                TASK_BOUNDS.left,
+                TASK_BOUNDS.top,
+                TASK_BOUNDS.right,
+                hingeBounds.top
+        );
+        final Rect secondaryBounds = new Rect(
+                TASK_BOUNDS.left,
+                hingeBounds.bottom,
+                TASK_BOUNDS.right,
+                TASK_BOUNDS.bottom
+        );
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
     }
 
     @Test
     public void testExpandSplitContainerIfNeeded() {
-        SplitContainer splitContainer = mock(SplitContainer.class);
         Activity secondaryActivity = createMockActivity();
         SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
         TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
         TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
-        doReturn(splitRule).when(splitContainer).getSplitRule();
-        doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
-        doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
+        SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity,
+                secondaryTf, splitRule, SPLIT_ATTRIBUTES);
 
         assertThrows(IllegalArgumentException.class, () ->
                 mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
@@ -224,11 +441,13 @@
                 splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
         verify(mPresenter, never()).expandTaskFragment(any(), any());
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
         assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
                 mTransaction, splitContainer, mActivity, secondaryActivity,
                 null /* secondaryIntent */));
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
         secondaryTf.setInfo(mTransaction,
                 createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -238,6 +457,7 @@
         verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
         verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         clearInvocations(mPresenter);
 
         assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -256,6 +476,7 @@
         final SplitPairRule rule = new SplitPairRule.Builder(pair ->
                 pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
                 metrics -> true)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .setShouldClearTop(false)
                 .build();
 
@@ -268,6 +489,49 @@
         assertTrue(secondaryTf.isAbove(primaryTf));
     }
 
+    @Test
+    public void testComputeSplitAttributes() {
+        final SplitPairRule splitPairRule = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+
+        assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+
+        final Pair<Size, Size> minDimensionsPair = new Pair<>(
+                new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
+
+        assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, minDimensionsPair));
+
+        taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
+                TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
+                TASK_BOUNDS.bottom + 1));
+
+        assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(
+                        new SplitAttributes.SplitType.HingeSplitType(
+                                SplitAttributes.SplitType.RatioSplitType.splitEqually()
+                        )
+                ).build();
+
+        mController.setSplitAttributesCalculator(params -> {
+            return splitAttributes;
+        });
+
+        assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+    }
+
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
         final Configuration activityConfig = new Configuration();
@@ -279,4 +543,10 @@
         doReturn(mock(IBinder.class)).when(activity).getActivityToken();
         return activity;
     }
+
+    private static TaskContainer.TaskProperties getTaskProperty() {
+        final Configuration configuration = new Configuration();
+        configuration.windowConfiguration.setBounds(TASK_BOUNDS);
+        return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
+    }
 }
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 dd67e48..af9c6ba 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
@@ -21,9 +21,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,8 +35,10 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentParentInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -66,7 +69,7 @@
 
     @Test
     public void testIsTaskBoundsInitialized() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertFalse(taskContainer.isTaskBoundsInitialized());
 
@@ -77,7 +80,7 @@
 
     @Test
     public void testSetTaskBounds() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertFalse(taskContainer.setTaskBounds(new Rect()));
 
@@ -87,30 +90,24 @@
     }
 
     @Test
-    public void testIsWindowingModeInitialized() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
-
-        assertFalse(taskContainer.isWindowingModeInitialized());
-
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
-        assertTrue(taskContainer.isWindowingModeInitialized());
-    }
-
-    @Test
     public void testGetWindowingModeForSplitTaskFragment() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final Rect splitBounds = new Rect(0, 0, 500, 1000);
+        final Configuration configuration = new Configuration();
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -123,22 +120,27 @@
 
     @Test
     public void testIsInPictureInPicture() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final Configuration configuration = new Configuration();
 
         assertFalse(taskContainer.isInPictureInPicture());
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertFalse(taskContainer.isInPictureInPicture());
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_PINNED);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertTrue(taskContainer.isInPictureInPicture());
     }
 
     @Test
     public void testIsEmpty() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertTrue(taskContainer.isEmpty());
 
@@ -155,7 +157,7 @@
 
     @Test
     public void testGetTopTaskFragmentContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         assertNull(taskContainer.getTopTaskFragmentContainer());
 
         final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
@@ -169,7 +171,7 @@
 
     @Test
     public void testGetTopNonFinishingActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         assertNull(taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 082774e..35415d8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -16,8 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -90,7 +90,7 @@
 
     @Test
     public void testNewContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         // One of the activity and the intent must be non-null
         assertThrows(IllegalArgumentException.class,
@@ -103,40 +103,39 @@
 
     @Test
     public void testFinish() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
         doReturn(container).when(mController).getContainerWithActivity(mActivity);
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         // Only remove the activity, but not clear the reference until appeared.
-        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+        container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
 
-        verify(mActivity).finish();
+        verify(mTransaction).finishActivity(mActivity.getActivityToken());
         verify(mPresenter, never()).deleteTaskFragment(any(), any());
         verify(mController, never()).removeContainer(any());
 
         // Calling twice should not finish activity again.
-        clearInvocations(mActivity);
-        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+        clearInvocations(mTransaction);
+        container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
 
-        verify(mActivity, never()).finish();
+        verify(mTransaction, never()).finishActivity(any());
         verify(mPresenter, never()).deleteTaskFragment(any(), any());
         verify(mController, never()).removeContainer(any());
 
         // Remove all references after the container has appeared in server.
         doReturn(new ArrayList<>()).when(mInfo).getActivities();
         container.setInfo(mTransaction, mInfo);
-        container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+        container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
 
-        verify(mActivity, never()).finish();
-        verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken());
+        verify(mTransaction, never()).finishActivity(any());
+        verify(mPresenter).deleteTaskFragment(mTransaction, container.getTaskFragmentToken());
         verify(mController).removeContainer(container);
     }
 
     @Test
     public void testFinish_notFinishActivityThatIsReparenting() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
         final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
@@ -150,14 +149,14 @@
         // The activity is requested to be reparented, so don't finish it.
         container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
 
-        verify(mActivity, never()).finish();
+        verify(mTransaction, never()).finishActivity(any());
         verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
         verify(mController).removeContainer(container0);
     }
 
     @Test
     public void testSetInfo() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         // Pending activity should be cleared when it has appeared on server side.
         final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
@@ -185,7 +184,7 @@
 
     @Test
     public void testIsWaitingActivityAppear() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
 
@@ -207,7 +206,7 @@
     @Test
     public void testAppearEmptyTimeout() {
         doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
 
@@ -247,7 +246,7 @@
 
     @Test
     public void testCollectNonFinishingActivities() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         List<Activity> activities = container.collectNonFinishingActivities();
@@ -275,7 +274,7 @@
 
     @Test
     public void testAddPendingActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
@@ -289,7 +288,7 @@
 
     @Test
     public void testIsAbove() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
@@ -301,7 +300,7 @@
 
     @Test
     public void testGetBottomMostActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
@@ -318,7 +317,7 @@
 
     @Test
     public void testOnActivityDestroyed() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec..82573b2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,7 @@
     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",
     ],
     path: "src",
 }
@@ -100,6 +101,21 @@
     out: ["wm_shell_protolog.json"],
 }
 
+genrule {
+    name: "protolog.json.gz",
+    srcs: [":generate-wm_shell_protolog.json"],
+    out: ["wmshell.protolog.json.gz"],
+    cmd: "$(location minigzip) -c < $(in) > $(out)",
+    tools: ["minigzip"],
+}
+
+prebuilt_etc {
+    name: "wmshell.protolog.json.gz",
+    system_ext_specific: true,
+    src: ":protolog.json.gz",
+    filename_from_src: true,
+}
+
 // End ProtoLog
 
 java_library {
@@ -123,9 +139,6 @@
     resource_dirs: [
         "res",
     ],
-    java_resources: [
-        ":generate-wm_shell_protolog.json",
-    ],
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index afd3aac..70755e6 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -119,7 +119,7 @@
 
     <!-- Temporarily extending the background to show an edu text hint for opening the menu -->
     <FrameLayout
-        android:id="@+id/tv_pip_menu_edu_text_container"
+        android:id="@+id/tv_pip_menu_edu_text_drawer_placeholder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_below="@+id/tv_pip"
@@ -127,23 +127,8 @@
         android:layout_alignStart="@+id/tv_pip"
         android:layout_alignEnd="@+id/tv_pip"
         android:background="@color/tv_pip_menu_background"
-        android:clipChildren="true">
-
-        <TextView
-            android:id="@+id/tv_pip_menu_edu_text"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/pip_menu_edu_text_view_height"
-            android:layout_gravity="bottom|center"
-            android:gravity="center"
-            android:clickable="false"
-            android:paddingBottom="@dimen/pip_menu_border_width"
-            android:text="@string/pip_edu_text"
-            android:singleLine="true"
-            android:ellipsize="marquee"
-            android:marqueeRepeatLimit="1"
-            android:scrollHorizontally="true"
-            android:textAppearance="@style/TvPipEduText"/>
-    </FrameLayout>
+        android:paddingBottom="@dimen/pip_menu_border_width"
+        android:paddingTop="@dimen/pip_menu_border_width"/>
 
     <!-- Frame around the PiP content + edu text hint - used to highlight open menu -->
     <View
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 40c4c35..36c24c1b 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index a183a0b..dff8f3f 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</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="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 98f11dd..fa9d2c2 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</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="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 18cb3ff..039b7e2 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</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="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 2040288..3622918 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index b2b484e..e65268a 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 59db992..31fcc17 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</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="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 8b5c4a9..0944d21 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</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="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index d7ff018..87eb9ff 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</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="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index f4c4560..01463c2 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index c4e6b0d..c8d0bcc 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 8b0d1ff..7012294 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 94faf41..e3c99ae 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 3971445..d231b63 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 023c2d6..0a4f88a 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</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="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index b5b6670..042bc8a 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index e38fc09..9234ad2 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f468452..ea5005d 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 2c1ee82..1e5e485 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 6b7bf4d..df43d55 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</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="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index b82a7cc..a4acec4 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 449dc27..acc97f8 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 15148b7..d063f71 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index b848fd0..2cd8a4a 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 79d27a2..2ade063 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</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="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index e803abe..0fd83d3 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</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="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 07b946d..6ea911d 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 08b35bf..e149f5c 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 9d81e8c..070fb94 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</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="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index f74e83f..b5a1de1 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 71f6ec7..4e935a2 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 4583135..c4b5721 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index bd52cd8..edd2cb64 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</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="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 98b3ba6..721ef6c 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index fbf0b5ec..d4aaba0 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</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="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index a75dc3c..a4ff2a9 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</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="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 6913c06..47367f5 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</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="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 1f246d5..001e122 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಎಳೆಯಿರಿ"</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="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index e878cef..27e294e 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</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="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 20c462e..d46fb66 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</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="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 3f4a881..d7d34d7 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</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="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 515a263..4b16f63 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 080c6f4..36743cf 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 47ed632..52a9377 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</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="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index faa6a30..343ccf1 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</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="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 8e8d06d..5370ef6 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</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="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 4fc03b2..1433ce4 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</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="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 2db225a..04805dac 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index e8ab4b0..092cea2 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</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="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 278de2d..22fa7f2 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 529f401..9502421 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</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="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 88c220c..37fe1fd 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 2c82fbc..ca31f3c 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</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="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 8c7229f..1f118c9 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</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="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 315b95e..4171aeb 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 48781ad..7a62410 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index e2be183..0054902 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 48781ad..7a62410 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 65b0472..ba95378 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -17,21 +17,21 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="pip_phone_close" msgid="5783752637260411309">"Închideți"</string>
-    <string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string>
+    <string name="pip_phone_close" msgid="5783752637260411309">"Închide"</string>
+    <string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string>
     <string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string>
-    <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string>
+    <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
     <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meniu picture-in-picture"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string>
-    <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu doriți ca <xliff:g id="NAME">%s</xliff:g> să utilizeze această funcție, atingeți pentru a deschide setările și dezactivați-o."</string>
-    <string name="pip_play" msgid="3496151081459417097">"Redați"</string>
-    <string name="pip_pause" msgid="690688849510295232">"Întrerupeți"</string>
-    <string name="pip_skip_to_next" msgid="8403429188794867653">"Treceți la următorul"</string>
-    <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treceți la cel anterior"</string>
-    <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionați"</string>
-    <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stocați"</string>
-    <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulați stocarea"</string>
+    <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu vrei ca <xliff:g id="NAME">%s</xliff:g> să folosească această funcție, atinge pentru a deschide setările și dezactiveaz-o."</string>
+    <string name="pip_play" msgid="3496151081459417097">"Redă"</string>
+    <string name="pip_pause" msgid="690688849510295232">"Întrerupe"</string>
+    <string name="pip_skip_to_next" msgid="8403429188794867653">"Treci la următorul"</string>
+    <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treci la cel anterior"</string>
+    <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="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
@@ -49,44 +49,41 @@
     <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>
     <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, glisați în sus din partea de jos a ecranului sau atingeți oriunde deasupra ferestrei aplicației"</string>
-    <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activați modul cu o mână"</string>
-    <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Părăsiți modul 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>
+    <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Ieși din modul cu o mână"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Setări pentru baloanele <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Suplimentar"</string>
-    <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adăugați înapoi în stivă"</string>
+    <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adaugă înapoi în stivă"</string>
     <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <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> de la <xliff:g id="APP_NAME">%2$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>"</string>
-    <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mutați în stânga sus"</string>
-    <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mutați în dreapta sus"</string>
-    <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mutați în stânga jos"</string>
-    <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mutați în dreapta jos"</string>
+    <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mută în stânga sus"</string>
+    <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mută în dreapta sus"</string>
+    <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mută în stânga jos"</string>
+    <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string>
     <string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
-    <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închideți balonul"</string>
-    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișați conversația în balon"</string>
+    <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
+    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string>
-    <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atingeți pentru a deschide balonul. Trageți pentru a-l muta."</string>
-    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlați oricând baloanele"</string>
-    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atingeți Gestionați pentru a dezactiva baloanele din această aplicație"</string>
+    <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string>
+    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlează oricând baloanele"</string>
+    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atinge Gestionează pentru a dezactiva baloanele din această aplicație"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
-    <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</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ți ca să reporniți aplicația pentru o vizualizare mai bună."</string>
-    <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Aveți probleme cu camera foto?\nAtingeți pentru a reîncadra"</string>
-    <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ați remediat problema?\nAtingeți pentru a reveni"</string>
-    <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu aveți probleme cu camera foto? Atingeți pentru a închide."</string>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare 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_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ți pentru mai multe informații"</string>
-    <string name="maximize_button_text" msgid="1650859196290301963">"Maximizați"</string>
+    <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</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ți"</string>
+    <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index 36df286..b5245ff 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -26,8 +26,8 @@
     <string name="pip_collapse" msgid="3903295106641385962">"Restrânge"</string>
     <string name="pip_edu_text" msgid="3672999496647508701">" Apasă de două ori "<annotation icon="home_icon">"butonul ecran de pornire"</annotation>" pentru comenzi"</string>
     <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meniu picture-in-picture."</string>
-    <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mută spre stânga"</string>
-    <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mută spre dreapta"</string>
+    <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mută la stânga"</string>
+    <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mută la dreapta"</string>
     <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mută în sus"</string>
     <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mută în jos"</string>
     <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gata"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 8affb9a..1a77e42 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</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="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index c816065..dc89ec3 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</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="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 9dfbd54..aec8501 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 5bf943b..44462b6 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 0955999..6e26ec6 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index b42d98e4..94725cb 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</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="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 162b57d..6b6ba2b 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 3844d01..102e9cf 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index c45409c..c2166fd 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</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="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 0a61f93..ef0f9e7 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</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="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 0a41d45..7a7575d 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</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="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index e272799..1c8d94f 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 050fa5f..82e3f58 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index b45b9ec..9833a88 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -41,8 +41,10 @@
     <dimen name="pip_menu_edu_text_view_height">24dp</dimen>
     <dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
     <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
-    <integer name="pip_edu_text_show_duration_ms">10500</integer>
-    <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
-    <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
+    <integer name="pip_edu_text_scroll_times">2</integer>
+    <integer name="pip_edu_text_non_scroll_show_duration">10500</integer>
+    <integer name="pip_edu_text_start_scroll_delay">2000</integer>
+    <integer name="pip_edu_text_window_exit_animation_duration">1000</integer>
+    <integer name="pip_edu_text_view_exit_animation_duration">300</integer>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index d5f047f..218d11e 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 700ecaa..4a9c079 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</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="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index e843b0b..a063476 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index ec1eadb..b472965 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 37d4244..d7366952 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 170cd4c..8eda853 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 83f8520..71f4f2b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -78,12 +78,9 @@
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
+    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</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="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 686310a..f637912 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -78,12 +78,9 @@
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string>
     <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>
-    <!-- no translation found for letterbox_education_dialog_title (7739895354143295358) -->
-    <skip />
-    <!-- no translation found for letterbox_education_split_screen_text (6206339484068670830) -->
-    <skip />
-    <!-- no translation found for letterbox_education_reposition_text (4589957299813220661) -->
-    <skip />
+    <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_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>
     <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index f03b7f6..30c3d50 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -19,6 +19,10 @@
          by the resources of the app using the Shell library. -->
     <bool name="config_enableShellMainThread">false</bool>
 
+    <!-- Determines whether to register the shell task organizer on init.
+         TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+    <bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+
     <!-- Animation duration for PIP when entering. -->
     <integer name="config_pipEnterAnimationDuration">425</integer>
 
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 5696b8d..0bc7085 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -297,4 +297,28 @@
       when the pinned stack size is overridden by app via minWidth/minHeight.
     -->
     <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
+
+    <!-- The size of the drag handle / menu shown along with a floating task. -->
+    <dimen name="floating_task_menu_size">32dp</dimen>
+
+    <!-- The size of menu items in the floating task menu. -->
+    <dimen name="floating_task_menu_item_size">24dp</dimen>
+
+    <!-- The horizontal margin of menu items in the floating task menu. -->
+    <dimen name="floating_task_menu_item_padding">5dp</dimen>
+
+    <!-- The width of visible floating view region when stashed. -->
+    <dimen name="floating_task_stash_offset">32dp</dimen>
+
+    <!-- The amount of elevation for a floating task. -->
+    <dimen name="floating_task_elevation">8dp</dimen>
+
+    <!-- The amount of padding around the bottom and top of the task. -->
+    <dimen name="floating_task_vertical_padding">8dp</dimen>
+
+    <!-- The normal size of the dismiss target. -->
+    <dimen name="floating_task_dismiss_circle_size">150dp</dimen>
+
+    <!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
+    <dimen name="floating_dismiss_circle_small">120dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml
index 2b7a13e..8f806cf 100644
--- a/libs/WindowManager/Shell/res/values/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values/strings_tv.xml
@@ -42,8 +42,8 @@
 
     <!-- Educative text instructing the user to double press the HOME button to access the pip
         controls menu [CHAR LIMIT=50] -->
-    <string name="pip_edu_text"> Double press <annotation icon="home_icon"> HOME </annotation> for
-        controls </string>
+    <string name="pip_edu_text">Double press <annotation icon="home_icon">HOME</annotation> for
+        controls</string>
 
     <!-- Accessibility announcement when opening the PiP menu. [CHAR LIMIT=NONE] -->
     <string name="a11y_pip_menu_entered">Picture-in-Picture menu.</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d88cc00..756d802 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -18,6 +18,10 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+
+import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -41,7 +45,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import java.util.function.BiFunction;
 
 /** To run the ActivityEmbedding animations. */
 class ActivityEmbeddingAnimationRunner {
@@ -84,7 +87,7 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Runnable animationFinishCallback) {
         final List<ActivityEmbeddingAnimationAdapter> adapters =
-                createAnimationAdapters(info, startTransaction);
+                createAnimationAdapters(info, startTransaction, finishTransaction);
         long duration = 0;
         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
             duration = Math.max(duration, adapter.getDurationHint());
@@ -128,31 +131,42 @@
      */
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        boolean isChangeTransition = false;
         for (TransitionInfo.Change change : info.getChanges()) {
-            if (change.getMode() == TRANSIT_CHANGE
+            if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
+                // Skip the animation if the windows are behind an app starting window.
+                return new ArrayList<>();
+            }
+            if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE
                     && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
-                return createChangeAnimationAdapters(info, startTransaction);
+                isChangeTransition = true;
             }
         }
-        if (Transitions.isClosingType(info.getType())) {
-            return createCloseAnimationAdapters(info);
+        if (isChangeTransition) {
+            return createChangeAnimationAdapters(info, startTransaction);
         }
-        return createOpenAnimationAdapters(info);
+        if (Transitions.isClosingType(info.getType())) {
+            return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
+        }
+        return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
-            @NonNull TransitionInfo info) {
-        return createOpenCloseAnimationAdapters(info, true /* isOpening */,
-                mAnimationSpec::loadOpenAnimation);
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
+                true /* isOpening */, mAnimationSpec::loadOpenAnimation);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
-            @NonNull TransitionInfo info) {
-        return createOpenCloseAnimationAdapters(info, false /* isOpening */,
-                mAnimationSpec::loadCloseAnimation);
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
+                false /* isOpening */, mAnimationSpec::loadCloseAnimation);
     }
 
     /**
@@ -161,8 +175,9 @@
      */
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
-            @NonNull TransitionInfo info, boolean isOpening,
-            @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
+            @NonNull AnimationProvider animationProvider) {
         // We need to know if the change window is only a partial of the whole animation screen.
         // If so, we will need to adjust it to make the whole animation screen looks like one.
         final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -185,7 +200,8 @@
         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
         for (TransitionInfo.Change change : openingChanges) {
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
-                    change, animationProvider, openingWholeScreenBounds);
+                    info, change, startTransaction, finishTransaction, animationProvider,
+                    openingWholeScreenBounds);
             if (isOpening) {
                 adapter.overrideLayer(offsetLayer++);
             }
@@ -193,7 +209,8 @@
         }
         for (TransitionInfo.Change change : closingChanges) {
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
-                    change, animationProvider, closingWholeScreenBounds);
+                    info, change, startTransaction, finishTransaction, animationProvider,
+                    closingWholeScreenBounds);
             if (!isOpening) {
                 adapter.overrideLayer(offsetLayer++);
             }
@@ -204,10 +221,18 @@
 
     @NonNull
     private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
-            @NonNull TransitionInfo.Change change,
-            @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
-            @NonNull Rect wholeAnimationBounds) {
-        final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
+            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
+        final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
+        // We may want to show a background color for open/close transition.
+        final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
+                0 /* defaultColor */);
+        if (backgroundColor != 0) {
+            addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
+                    finishTransaction);
+        }
         return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
                 wholeAnimationBounds);
     }
@@ -276,7 +301,8 @@
             }
 
             final Animation animation;
-            if (!TransitionInfo.isIndependent(change, info)) {
+            if (change.getParent() != null
+                    && handledChanges.contains(info.getChange(change.getParent()))) {
                 // No-op if it will be covered by the changing parent window.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (Transitions.isClosingType(change.getMode())) {
@@ -312,4 +338,10 @@
         return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
                 animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
     }
+
+    /** To provide an {@link Animation} based on the transition infos. */
+    private interface AnimationProvider {
+        Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+                @NonNull Rect animationBounds);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index ad0dddf..eb6ac76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -17,6 +17,9 @@
 package com.android.wm.shell.activityembedding;
 
 
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -33,7 +36,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.R;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.wm.shell.transition.Transitions;
 
@@ -175,16 +177,20 @@
     }
 
     @NonNull
-    Animation loadOpenAnimation(@NonNull TransitionInfo.Change change,
-            @NonNull Rect wholeAnimationBounds) {
+    Animation loadOpenAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = Transitions.isOpeningType(change.getMode());
         final Animation animation;
-        // TODO(b/207070762):
-        // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
-        // 2. Implement edgeExtension version
-        animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                ? R.anim.task_fragment_open_enter
-                : R.anim.task_fragment_open_exit);
+        // TODO(b/207070762): Implement edgeExtension version
+        if (shouldShowBackdrop(info, change)) {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
+                    : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
+        } else {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_open_enter
+                    : com.android.internal.R.anim.task_fragment_open_exit);
+        }
         // Use the whole animation bounds instead of the change bounds, so that when multiple change
         // targets are opening at the same time, the animation applied to each will be the same.
         // Otherwise, we may see gap between the activities that are launching together.
@@ -195,16 +201,20 @@
     }
 
     @NonNull
-    Animation loadCloseAnimation(@NonNull TransitionInfo.Change change,
-            @NonNull Rect wholeAnimationBounds) {
+    Animation loadCloseAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = Transitions.isOpeningType(change.getMode());
         final Animation animation;
-        // TODO(b/207070762):
-        // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
-        // 2. Implement edgeExtension version
-        animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                ? R.anim.task_fragment_close_enter
-                : R.anim.task_fragment_close_exit);
+        // TODO(b/207070762): Implement edgeExtension version
+        if (shouldShowBackdrop(info, change)) {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
+                    : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
+        } else {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_close_enter
+                    : com.android.internal.R.anim.task_fragment_close_exit);
+        }
         // Use the whole animation bounds instead of the change bounds, so that when multiple change
         // targets are closing at the same time, the animation applied to each will be the same.
         // Otherwise, we may see gap between the activities that are finishing together.
@@ -213,4 +223,11 @@
         animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         return animation;
     }
+
+    private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change) {
+        final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
+                mTransitionAnimation);
+        return a != null && a.getShowBackdrop();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b..8cbe44b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@
 public interface BackAnimation {
 
     /**
-     * Returns a binder that can be passed to an external process to update back animations.
-     */
-    default IBackAnimation createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Called when a {@link MotionEvent} is generated by a back gesture.
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ebf8c03..43f39b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -18,18 +18,15 @@
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
-import android.app.WindowConfiguration;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.hardware.HardwareBuffer;
 import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Handler;
@@ -40,24 +37,31 @@
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.util.Log;
+import android.util.SparseArray;
+import android.view.IRemoteAnimationRunner;
 import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
+import android.window.BackAnimationAdapter;
 import android.window.BackEvent;
 import android.window.BackNavigationInfo;
+import android.window.IBackAnimationFinishedCallback;
+import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -75,6 +79,9 @@
                     SETTING_VALUE_ON) != SETTING_VALUE_OFF;
     private static final int PROGRESS_THRESHOLD = SystemProperties
             .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+
+    // TODO (b/241808055) Find a appropriate time to remove during refactor
+    private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
     /**
      * Max duration to wait for a transition to finish before accepting another gesture start
      * request.
@@ -83,16 +90,6 @@
 
     private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
 
-    /**
-     * Location of the initial touch event of the back gesture.
-     */
-    private final PointF mInitTouchLocation = new PointF();
-
-    /**
-     * Raw delta between {@link #mInitTouchLocation} and the last touch location.
-     */
-    private final Point mTouchEventDelta = new Point();
-
     /** True when a back gesture is ongoing */
     private boolean mBackGestureStarted = false;
 
@@ -105,23 +102,28 @@
 
     @Nullable
     private BackNavigationInfo mBackNavigationInfo;
-    private final SurfaceControl.Transaction mTransaction;
     private final IActivityTaskManager mActivityTaskManager;
     private final Context mContext;
     private final ContentResolver mContentResolver;
+    private final ShellController mShellController;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
-    @Nullable
-    private IOnBackInvokedCallback mBackToLauncherCallback;
-    private float mTriggerThreshold;
-    private float mProgressThreshold;
     private final Runnable mResetTransitionRunnable = () -> {
-        finishAnimation();
-        mTransitionInProgress = false;
         ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
                 MAX_TRANSITION_DURATION);
+        onBackAnimationFinished();
     };
 
+    private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
+    @VisibleForTesting
+    BackAnimationAdapter mBackAnimationAdapter;
+
+    private final TouchTracker mTouchTracker = new TouchTracker();
+
+    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+    private final Transitions mTransitions;
+    private BackTransitionHandler mBackTransitionHandler;
+
     @VisibleForTesting
     final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
         @Override
@@ -134,40 +136,100 @@
                     // this due to the transition may cause focus lost. (alpha = 0)
                     return;
                 }
+                ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
                 setTriggerBack(false);
                 onGestureFinished(false);
             });
         }
     };
 
+    /**
+     * Helper class to record the touch location for gesture start and latest.
+     */
+    private static class TouchTracker {
+        /**
+         * Location of the latest touch event
+         */
+        private float mLatestTouchX;
+        private float mLatestTouchY;
+        private int mSwipeEdge;
+        private float mProgressThreshold;
+
+        /**
+         * Location of the initial touch event of the back gesture.
+         */
+        private float mInitTouchX;
+        private float mInitTouchY;
+
+        void update(float touchX, float touchY, int swipeEdge) {
+            mLatestTouchX = touchX;
+            mLatestTouchY = touchY;
+            mSwipeEdge = swipeEdge;
+        }
+
+        void setGestureStartLocation(float touchX, float touchY) {
+            mInitTouchX = touchX;
+            mInitTouchY = touchY;
+        }
+
+        void setProgressThreshold(float progressThreshold) {
+            mProgressThreshold = progressThreshold;
+        }
+
+        float getProgress(float touchX) {
+            int deltaX = Math.round(touchX - mInitTouchX);
+            float progressThreshold = PROGRESS_THRESHOLD >= 0
+                    ? PROGRESS_THRESHOLD : mProgressThreshold;
+            return Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
+        }
+
+        void reset() {
+            mInitTouchX = 0;
+            mInitTouchY = 0;
+            mSwipeEdge = -1;
+        }
+    }
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
-            Context context) {
-        this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
-                ActivityTaskManager.getService(), context, context.getContentResolver());
+            Context context,
+            Transitions transitions) {
+        this(shellInit, shellController, shellExecutor, backgroundHandler,
+                ActivityTaskManager.getService(), context, context.getContentResolver(),
+                transitions);
     }
 
     @VisibleForTesting
     BackAnimationController(
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
-            @NonNull SurfaceControl.Transaction transaction,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context, ContentResolver contentResolver) {
+            Context context, ContentResolver contentResolver,
+            Transitions transitions) {
+        mShellController = shellController;
         mShellExecutor = shellExecutor;
-        mTransaction = transaction;
         mActivityTaskManager = activityTaskManager;
         mContext = context;
         mContentResolver = contentResolver;
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
+        mTransitions = transitions;
     }
 
     private void onInit() {
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+        createAdapter();
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mBackTransitionHandler = new BackTransitionHandler(this);
+            mTransitions.addHandler(mBackTransitionHandler);
+        }
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+                this::createExternalInterface, this);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -200,7 +262,11 @@
         return mBackAnimation;
     }
 
-    private final BackAnimation mBackAnimation = new BackAnimationImpl();
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IBackAnimationImpl(this);
+    }
+
+    private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
 
     @Override
     public Context getContext() {
@@ -213,17 +279,6 @@
     }
 
     private class BackAnimationImpl implements BackAnimation {
-        private IBackAnimationImpl mBackAnimation;
-
-        @Override
-        public IBackAnimation createExternalInterface() {
-            if (mBackAnimation != null) {
-                mBackAnimation.invalidate();
-            }
-            mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
-            return mBackAnimation;
-        }
-
         @Override
         public void onBackMotion(
                 float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -242,7 +297,8 @@
         }
     }
 
-    private static class IBackAnimationImpl extends IBackAnimation.Stub {
+    private static class IBackAnimationImpl extends IBackAnimation.Stub
+            implements ExternalInterfaceBinder {
         private BackAnimationController mController;
 
         IBackAnimationImpl(BackAnimationController controller) {
@@ -250,9 +306,10 @@
         }
 
         @Override
-        public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+        public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
+                IRemoteAnimationRunner runner) {
             executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
-                    (controller) -> controller.setBackToLauncherCallback(callback));
+                    (controller) -> controller.setBackToLauncherCallback(callback, runner));
         }
 
         @Override
@@ -262,27 +319,30 @@
         }
 
         @Override
-        public void onBackToLauncherAnimationFinished() {
-            executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished",
-                    (controller) -> controller.onBackToLauncherAnimationFinished());
-        }
-
-        void invalidate() {
+        public void invalidate() {
             mController = null;
         }
     }
 
     @VisibleForTesting
-    void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
-        mBackToLauncherCallback = callback;
+    void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                new BackAnimationRunner(callback, runner));
     }
 
     private void clearBackToLauncherCallback() {
-        mBackToLauncherCallback = null;
+        mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
     }
 
     @VisibleForTesting
-    void onBackToLauncherAnimationFinished() {
+    void onBackAnimationFinished() {
+        if (!mTransitionInProgress) {
+            return;
+        }
+
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+        // Trigger real back.
         if (mBackNavigationInfo != null) {
             IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
             if (mTriggerBack) {
@@ -291,7 +351,18 @@
                 dispatchOnBackCancelled(callback);
             }
         }
-        finishAnimation();
+
+        // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
+        // following transition when back callback is invoked.
+        // If the back callback is not invoked, we should reset the token and finish the whole back
+        // navigation without waiting the transition.
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            finishBackNavigation();
+        } else if (!mTriggerBack) {
+            // reset the token to prevent it consume next transition.
+            mBackTransitionHandler.setDepartingWindowContainerToken(null);
+            finishBackNavigation();
+        }
     }
 
     /**
@@ -303,6 +374,7 @@
         if (mTransitionInProgress) {
             return;
         }
+        mTouchTracker.update(touchX, touchY, swipeEdge);
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
                 mShouldStartOnNextMoveEvent = true;
@@ -330,20 +402,19 @@
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
         if (mBackGestureStarted || mBackNavigationInfo != null) {
             Log.e(TAG, "Animation is being initialized but is already started.");
-            finishAnimation();
+            finishBackNavigation();
         }
 
-        mInitTouchLocation.set(touchX, touchY);
+        mTouchTracker.setGestureStartLocation(touchX, touchY);
         mBackGestureStarted = true;
 
         try {
-            boolean requestAnimation = mEnableAnimations.get();
-            mBackNavigationInfo =
-                    mActivityTaskManager.startBackNavigation(requestAnimation, mFocusObserver);
+            mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
+                    mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
             onBackNavigationInfoReceived(mBackNavigationInfo);
         } catch (RemoteException remoteException) {
             Log.e(TAG, "Failed to initAnimation", remoteException);
-            finishAnimation();
+            finishBackNavigation();
         }
     }
 
@@ -353,74 +424,32 @@
             Log.e(TAG, "Received BackNavigationInfo is null.");
             return;
         }
-        int backType = backNavigationInfo.getType();
-        IOnBackInvokedCallback targetCallback = null;
-        if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
-            HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
-            if (hardwareBuffer != null) {
-                displayTargetScreenshot(hardwareBuffer,
-                        backNavigationInfo.getTaskWindowConfiguration());
-            }
-            mTransaction.apply();
-        } else if (shouldDispatchToLauncher(backType)) {
-            targetCallback = mBackToLauncherCallback;
-        } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+        final int backType = backNavigationInfo.getType();
+        final IOnBackInvokedCallback targetCallback;
+        final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+        if (shouldDispatchToAnimator) {
+            targetCallback = mAnimationDefinition.get(backType).getGestureStartedCallback();
+        } else {
             targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
         }
-        dispatchOnBackStarted(targetCallback);
-    }
-
-    /**
-     * Display the screenshot of the activity beneath.
-     *
-     * @param hardwareBuffer The buffer containing the screenshot.
-     */
-    private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer,
-            WindowConfiguration taskWindowConfiguration) {
-        SurfaceControl screenshotSurface =
-                mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface();
-        if (screenshotSurface == null) {
-            Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. ");
-            return;
+        if (shouldDispatchToAnimator) {
+            dispatchOnBackStarted(targetCallback);
         }
-
-        // Scale the buffer to fill the whole Task
-        float sx = 1;
-        float sy = 1;
-        float w = taskWindowConfiguration.getBounds().width();
-        float h = taskWindowConfiguration.getBounds().height();
-
-        if (w != hardwareBuffer.getWidth()) {
-            sx = w / hardwareBuffer.getWidth();
-        }
-
-        if (h != hardwareBuffer.getHeight()) {
-            sy = h / hardwareBuffer.getHeight();
-        }
-        mTransaction.setScale(screenshotSurface, sx, sy);
-        mTransaction.setBuffer(screenshotSurface, hardwareBuffer);
-        mTransaction.setVisibility(screenshotSurface, true);
     }
 
     private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
-        if (!mBackGestureStarted || mBackNavigationInfo == null) {
+        if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
             return;
         }
-        int deltaX = Math.round(touchX - mInitTouchLocation.x);
-        float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
-        float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
+        mTouchTracker.update(touchX, touchY, swipeEdge);
+        float progress = mTouchTracker.getProgress(touchX);
         int backType = mBackNavigationInfo.getType();
-        RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
 
-        BackEvent backEvent = new BackEvent(
-                touchX, touchY, progress, swipeEdge, animationTarget);
+        BackEvent backEvent = new BackEvent(touchX, touchY, progress, swipeEdge);
         IOnBackInvokedCallback targetCallback = null;
-        if (shouldDispatchToLauncher(backType)) {
-            targetCallback = mBackToLauncherCallback;
-        } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
-                || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
-            // TODO(208427216) Run the actual animation
-        } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+        if (shouldDispatchToAnimator(backType)) {
+            targetCallback = mAnimationDefinition.get(backType).getCallback();
+        } else {
             targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
         }
         dispatchOnBackProgressed(targetCallback, backEvent);
@@ -448,7 +477,7 @@
     private void onGestureFinished(boolean fromTouch) {
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
         if (!mBackGestureStarted) {
-            finishAnimation();
+            finishBackNavigation();
             return;
         }
 
@@ -468,16 +497,21 @@
             if (mTriggerBack) {
                 injectBackKey();
             }
-            finishAnimation();
+            finishBackNavigation();
             return;
         }
 
         int backType = mBackNavigationInfo.getType();
-        boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType);
-        IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
-                ? mBackToLauncherCallback
-                : mBackNavigationInfo.getOnBackInvokedCallback();
-        if (shouldDispatchToLauncher) {
+        boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+        IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
+                ? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
+
+        if (shouldDispatchToAnimator) {
+            if (runner.onGestureFinished(mTriggerBack)) {
+                Log.w(TAG, "Gesture released, but animation didn't ready.");
+                return;
+            }
             startTransition();
         }
         if (mTriggerBack) {
@@ -485,18 +519,17 @@
         } else {
             dispatchOnBackCancelled(targetCallback);
         }
-        if (backType != BackNavigationInfo.TYPE_RETURN_TO_HOME || !shouldDispatchToLauncher) {
-            // Launcher callback missing. Simply finish animation.
-            finishAnimation();
+        if (!shouldDispatchToAnimator) {
+            // Animation callback missing. Simply finish animation.
+            finishBackNavigation();
         }
     }
 
-    private boolean shouldDispatchToLauncher(int backType) {
-        return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
-                && mBackToLauncherCallback != null
-                && mEnableAnimations.get()
+    private boolean shouldDispatchToAnimator(int backType) {
+        return mEnableAnimations.get()
                 && mBackNavigationInfo != null
-                && mBackNavigationInfo.getDepartingAnimationTarget() != null;
+                && mBackNavigationInfo.isPrepareRemoteAnimation()
+                && mAnimationDefinition.contains(backType);
     }
 
     private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
@@ -555,35 +588,30 @@
     }
 
     private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
-        mProgressThreshold = progressThreshold;
-        mTriggerThreshold = triggerThreshold;
+        mTouchTracker.setProgressThreshold(progressThreshold);
     }
 
-    private void finishAnimation() {
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
-        mTouchEventDelta.set(0, 0);
-        mInitTouchLocation.set(0, 0);
+    @VisibleForTesting
+    void finishBackNavigation() {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
         BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
         boolean triggerBack = mTriggerBack;
         mBackNavigationInfo = null;
         mTriggerBack = false;
         mShouldStartOnNextMoveEvent = false;
+        mTouchTracker.reset();
         if (backNavigationInfo == null) {
             return;
         }
-
-        RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
-        if (animationTarget != null) {
-            if (animationTarget.leash != null && animationTarget.leash.isValid()) {
-                mTransaction.remove(animationTarget.leash);
-            }
-        }
-        SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
-        if (screenshotSurface != null && screenshotSurface.isValid()) {
-            mTransaction.remove(screenshotSurface);
-        }
-        mTransaction.apply();
         stopTransition();
+        if (mBackAnimationFinishedCallback != null) {
+            try {
+                mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
+            }
+            mBackAnimationFinishedCallback = null;
+        }
         backNavigationInfo.onBackNavigationFinished(triggerBack);
     }
 
@@ -592,14 +620,72 @@
             return;
         }
         mTransitionInProgress = true;
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mBackTransitionHandler.setDepartingWindowContainerToken(
+                    mBackNavigationInfo.getDepartingWindowContainerToken());
+        }
         mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
     }
 
-    private void stopTransition() {
-        if (!mTransitionInProgress) {
-            return;
-        }
+    void stopTransition() {
         mShellExecutor.removeCallbacks(mResetTransitionRunnable);
         mTransitionInProgress = false;
     }
+
+    /**
+     * This should be called from {@link BackTransitionHandler#startAnimation} when the following
+     * transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
+     * Will consume the default transition and finish current back navigation.
+     */
+    void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
+        mShellExecutor.execute(() -> {
+            finishBackNavigation();
+            finishCallback.onTransitionFinished(null, null);
+        });
+    }
+
+    private void createAdapter() {
+        IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
+            @Override
+            public void onAnimationStart(int type, RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                    IBackAnimationFinishedCallback finishedCallback) {
+                mShellExecutor.execute(() -> {
+                    final BackAnimationRunner runner = mAnimationDefinition.get(type);
+                    if (runner == null) {
+                        Log.e(TAG, "Animation didn't be defined for type "
+                                + BackNavigationInfo.typeToString(type));
+                        if (finishedCallback != null) {
+                            try {
+                                finishedCallback.onAnimationFinished(false);
+                            } catch (RemoteException e) {
+                                Log.w(TAG, "Failed call IBackNaviAnimationController", e);
+                            }
+                        }
+                        return;
+                    }
+                    mBackAnimationFinishedCallback = finishedCallback;
+
+                    ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
+                    runner.startAnimation(apps, wallpapers, nonApps,
+                            BackAnimationController.this::onBackAnimationFinished);
+
+                    if (!mBackGestureStarted) {
+                        // if the down -> up gesture happened before animation start, we have to
+                        // trigger the uninterruptible transition to finish the back animation.
+                        final BackEvent backFinish = new BackEvent(
+                                mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
+                                mTouchTracker.mSwipeEdge);
+                        startTransition();
+                        runner.consumeIfGestureFinished(backFinish);
+                    }
+                });
+            }
+
+            @Override
+            public void onAnimationCancelled() { }
+        };
+        mBackAnimationAdapter = new BackAnimationAdapter(runner);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
new file mode 100644
index 0000000..12bbf73
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.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.wm.shell.back;
+
+import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.window.BackEvent;
+import android.window.IBackAnimationRunner;
+import android.window.IOnBackInvokedCallback;
+
+/**
+ * Used to register the animation callback and runner, it will trigger result if gesture was finish
+ * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
+ * trigger the real back behavior.
+ */
+class BackAnimationRunner {
+    private static final String TAG = "ShellBackPreview";
+
+    private final IOnBackInvokedCallback mCallback;
+    private final IRemoteAnimationRunner mRunner;
+
+    private boolean mTriggerBack;
+    // Whether we are waiting to receive onAnimationStart
+    private boolean mWaitingAnimation;
+
+    BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+        mCallback = callback;
+        mRunner = runner;
+    }
+
+    /** Returns the registered animation runner */
+    IRemoteAnimationRunner getRunner() {
+        return mRunner;
+    }
+
+    /** Returns the registered animation callback */
+    IOnBackInvokedCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Called from {@link IBackAnimationRunner}, it will deliver these
+     * {@link RemoteAnimationTarget}s to the corresponding runner.
+     */
+    void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
+        final IRemoteAnimationFinishedCallback callback =
+                new IRemoteAnimationFinishedCallback.Stub() {
+                    @Override
+                    public void onAnimationFinished() {
+                        finishedCallback.run();
+                    }
+                };
+        mWaitingAnimation = false;
+        try {
+            mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
+                    nonApps, callback);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call onAnimationStart", e);
+        }
+    }
+
+    IOnBackInvokedCallback getGestureStartedCallback() {
+        mWaitingAnimation = true;
+        return mCallback;
+    }
+
+    boolean onGestureFinished(boolean triggerBack) {
+        if (mWaitingAnimation) {
+            mTriggerBack = triggerBack;
+            return true;
+        }
+        return false;
+    }
+
+    void consumeIfGestureFinished(final BackEvent backFinish) {
+        Log.d(TAG, "Start transition due to gesture is finished");
+        try {
+            mCallback.onBackProgressed(backFinish);
+            if (mTriggerBack) {
+                mCallback.onBackInvoked();
+            } else {
+                mCallback.onBackCancelled();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "dispatch error: ", e);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
new file mode 100644
index 0000000..6d72d9c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.transition.Transitions;
+
+class BackTransitionHandler implements Transitions.TransitionHandler {
+    private BackAnimationController mBackAnimationController;
+    private WindowContainerToken mDepartingWindowContainerToken;
+
+    BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
+        mBackAnimationController = backAnimationController;
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mDepartingWindowContainerToken != null) {
+            final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
+            if (change == null) {
+                return false;
+            }
+
+            startTransaction.hide(change.getLeash());
+            startTransaction.apply();
+            mDepartingWindowContainerToken = null;
+            mBackAnimationController.finishTransition(finishCallback);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+    }
+
+    void setDepartingWindowContainerToken(
+            @Nullable WindowContainerToken departingWindowContainerToken) {
+        mDepartingWindowContainerToken = departingWindowContainerToken;
+    }
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
index 6311f87..2b2a0e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
@@ -17,29 +17,21 @@
 package com.android.wm.shell.back;
 
 import android.window.IOnBackInvokedCallback;
+import android.view.IRemoteAnimationRunner;
 
 /**
  * Interface for Launcher process to register back invocation callbacks.
  */
 interface IBackAnimation {
-
     /**
-     * Sets a {@link IOnBackInvokedCallback} to be invoked when
+     * Sets a {@link IOnBackInvokedCallback} and a {@link IRemoteAnimationRunner} to be invoked when
      * back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}.
      */
-    void setBackToLauncherCallback(in IOnBackInvokedCallback callback);
+    void setBackToLauncherCallback(in IOnBackInvokedCallback callback,
+            in IRemoteAnimationRunner runner);
 
     /**
      * Clears the previously registered {@link IOnBackInvokedCallback}.
      */
     void clearBackToLauncherCallback();
-
-    /**
-     * Notifies Shell that the back to launcher animation has fully finished
-     * (including the transition animation that runs after the finger is lifted).
-     *
-     * At this point the top window leash (if one was created) should be ready to be released.
-     * //TODO: Remove once we play the transition animation through shell transitions.
-     */
-    void onBackToLauncherAnimationFinished();
 }
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 b5a5754..922472a 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
@@ -59,6 +59,8 @@
 public class Bubble implements BubbleViewProvider {
     private static final String TAG = "Bubble";
 
+    public static final String KEY_APP_BUBBLE = "key_app_bubble";
+
     private final String mKey;
     @Nullable
     private final String mGroupKey;
@@ -164,6 +166,14 @@
     private PendingIntent mDeleteIntent;
 
     /**
+     * Used only for a special bubble in the stack that has the key {@link #KEY_APP_BUBBLE}.
+     * There can only be one of these bubbles in the stack and this intent will be populated for
+     * that bubble.
+     */
+    @Nullable
+    private Intent mAppIntent;
+
+    /**
      * Create a bubble with limited information based on given {@link ShortcutInfo}.
      * Note: Currently this is only being used when the bubble is persisted to disk.
      */
@@ -192,6 +202,22 @@
         mBubbleMetadataFlagListener = listener;
     }
 
+    public Bubble(Intent intent,
+            UserHandle user,
+            Executor mainExecutor) {
+        mKey = KEY_APP_BUBBLE;
+        mGroupKey = null;
+        mLocusId = null;
+        mFlags = 0;
+        mUser = user;
+        mShowBubbleUpdateDot = false;
+        mMainExecutor = mainExecutor;
+        mTaskId = INVALID_TASK_ID;
+        mAppIntent = intent;
+        mDesiredHeight = Integer.MAX_VALUE;
+        mPackageName = intent.getPackage();
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final BubbleEntry entry,
             final Bubbles.BubbleMetadataFlagListener listener,
@@ -417,6 +443,9 @@
 
         mShortcutInfo = info.shortcutInfo;
         mAppName = info.appName;
+        if (mTitle == null) {
+            mTitle = mAppName;
+        }
         mFlyoutMessage = info.flyoutMessage;
 
         mBadgeBitmap = info.badgeBitmap;
@@ -520,7 +549,7 @@
      * @return the last time this bubble was updated or accessed, whichever is most recent.
      */
     long getLastActivity() {
-        return Math.max(mLastUpdated, mLastAccessed);
+        return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
@@ -719,6 +748,15 @@
         return mDeleteIntent;
     }
 
+    @Nullable
+    Intent getAppBubbleIntent() {
+        return mAppIntent;
+    }
+
+    boolean isAppBubble() {
+        return KEY_APP_BUBBLE.equals(mKey);
+    }
+
     Intent getSettingsIntent(final Context context) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
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 0dfba34..93413db 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
@@ -1017,6 +1017,20 @@
     }
 
     /**
+     * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
+     * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
+     * is supported at a time.
+     *
+     * @param intent the intent to display in the bubble expanded view.
+     */
+    public void addAppBubble(Intent intent) {
+        if (intent == null || intent.getPackage() == null) return;
+        Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+        b.setShouldAutoExpand(true);
+        inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+    }
+
+    /**
      * Fills the overflow bubbles by loading them from disk.
      */
     void loadOverflowBubblesFromDisk() {
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 54c91dd..8121b20 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
@@ -224,15 +224,23 @@
                 try {
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
-                    if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+
+                    Intent fillInIntent = new Intent();
+                    // Apply flags to make behaviour match documentLaunchMode=always.
+                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+                    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);
+                    } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                 options, launchBounds);
                     } else {
-                        Intent fillInIntent = new Intent();
-                        // Apply flags to make behaviour match documentLaunchMode=always.
-                        fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
-                        fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                         if (mBubble != null) {
                             mBubble.setIntentActive();
                         }
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
index 976fba5..e0c782d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
@@ -49,6 +49,8 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+        final Resources res = getResources();
+        setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
         setViewSizes();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
new file mode 100644
index 0000000..e029358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static android.content.Intent.EXTRA_DOCK_STATE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Provides information about the docked state of the device.
+ */
+@WMSingleton
+public class DockStateReader {
+
+    private static final IntentFilter DOCK_INTENT_FILTER = new IntentFilter(
+            Intent.ACTION_DOCK_EVENT);
+
+    private final Context mContext;
+
+    @Inject
+    public DockStateReader(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * @return True if the device is docked and false otherwise.
+     */
+    public boolean isDocked() {
+        Intent dockStatus = mContext.registerReceiver(/* receiver */ null, DOCK_INTENT_FILTER);
+        if (dockStatus != null) {
+            int dockState = dockStatus.getIntExtra(EXTRA_DOCK_STATE,
+                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            return dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
index 44c0496..aa5b0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -14,14 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.wm.shell.common;
+
+import android.os.IBinder;
 
 /**
- * Provides information on the current wifi activity.
+ * An interface for binders which can be registered to be sent to other processes.
  */
-data class WifiActivityModel(
-    /** True if the wifi has activity in (download). */
-    val hasActivityIn: Boolean,
-    /** True if the wifi has activity out (upload). */
-    val hasActivityOut: Boolean,
-)
+public interface ExternalInterfaceBinder {
+    /**
+     * Invalidates this binder (detaches it from the controller it would call).
+     */
+    void invalidate();
+
+    /**
+     * Returns the IBinder to send.
+     */
+    IBinder asBinder();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 235fd9c..6627de5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -37,6 +37,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
@@ -109,6 +110,7 @@
     private final SyncTransactionQueue mSyncQueue;
     private final ShellExecutor mMainExecutor;
     private final Lazy<Transitions> mTransitionsLazy;
+    private final DockStateReader mDockStateReader;
 
     private CompatUICallback mCallback;
 
@@ -127,7 +129,8 @@
             DisplayImeController imeController,
             SyncTransactionQueue syncQueue,
             ShellExecutor mainExecutor,
-            Lazy<Transitions> transitionsLazy) {
+            Lazy<Transitions> transitionsLazy,
+            DockStateReader dockStateReader) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -138,6 +141,7 @@
         mTransitionsLazy = transitionsLazy;
         mCompatUIHintsState = new CompatUIHintsState();
         shellInit.addInitCallback(this::onInit, this);
+        mDockStateReader = dockStateReader;
     }
 
     private void onInit() {
@@ -315,7 +319,8 @@
         return new LetterboxEduWindowManager(context, taskInfo,
                 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
                 mTransitionsLazy.get(),
-                this::onLetterboxEduDismissed);
+                this::onLetterboxEduDismissed,
+                mDockStateReader);
     }
 
     private void onLetterboxEduDismissed() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 35f1038..867d0ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -34,6 +34,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
 import com.android.wm.shell.transition.Transitions;
@@ -88,19 +89,21 @@
      */
     private final int mDialogVerticalMargin;
 
+    private final DockStateReader mDockStateReader;
+
     public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions,
-            Runnable onDismissCallback) {
+            Runnable onDismissCallback, DockStateReader dockStateReader) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
-                onDismissCallback, new LetterboxEduAnimationController(context));
+                onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader);
     }
 
     @VisibleForTesting
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
-            LetterboxEduAnimationController animationController) {
+            LetterboxEduAnimationController animationController, DockStateReader dockStateReader) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -111,6 +114,7 @@
                 Context.MODE_PRIVATE);
         mDialogVerticalMargin = (int) mContext.getResources().getDimension(
                 R.dimen.letterbox_education_dialog_margin);
+        mDockStateReader = dockStateReader;
     }
 
     @Override
@@ -130,13 +134,15 @@
 
     @Override
     protected boolean eligibleToShowLayout() {
+        // - The letterbox education should not be visible if the device is docked.
         // - If taskbar education is showing, the letterbox education shouldn't be shown for the
         //   given task until the taskbar education is dismissed and the compat info changes (then
         //   the controller will create a new instance of this class since this one isn't eligible).
         // - If the layout isn't null then it was previously showing, and we shouldn't check if the
         //   user has seen the letterbox education before.
-        return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
-                || !getHasSeenLetterboxEducation());
+        return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
+                && (mLayout != null || !getHasSeenLetterboxEducation())
+                && !mDockStateReader.isDocked();
     }
 
     @Override
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 7c3c14e..64dbfbb 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
@@ -24,10 +24,12 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
+import android.view.WindowManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ProtoLogController;
+import com.android.wm.shell.R;
 import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -44,6 +46,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.DockStateReader;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -56,14 +59,17 @@
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.floating.FloatingTasks;
+import com.android.wm.shell.floating.FloatingTasksController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -168,6 +174,7 @@
     @WMSingleton
     @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(
+            Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             CompatUIController compatUI,
@@ -175,39 +182,26 @@
             Optional<RecentTasksController> recentTasksOptional,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
+        if (!context.getResources().getBoolean(R.bool.config_registerShellTaskOrganizerOnInit)) {
+            // TODO(b/238217847): Force override shell init if registration is disabled
+            shellInit = new ShellInit(mainExecutor);
+        }
         return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
                 unfoldAnimationController, recentTasksOptional, mainExecutor);
     }
 
     @WMSingleton
     @Provides
-    static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
-            Context context,
-            ShellInit shellInit,
-            ShellCommandHandler shellCommandHandler,
-            SyncTransactionQueue syncTransactionQueue,
-            DisplayController displayController,
-            DisplayInsetsController displayInsetsController,
-            Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasksOptional,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler
-    ) {
-        return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
-                syncTransactionQueue, displayController, displayInsetsController,
-                unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
     static CompatUIController provideCompatUIController(Context context,
             ShellInit shellInit,
             ShellController shellController,
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
-            @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
+            @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
+            DockStateReader dockStateReader) {
         return new CompatUIController(context, shellInit, shellController, displayController,
-                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
+                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
+                dockStateReader);
     }
 
     @WMSingleton
@@ -267,13 +261,15 @@
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
-            @ShellBackgroundThread Handler backgroundHandler
+            @ShellBackgroundThread Handler backgroundHandler,
+            Transitions transitions
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
-                    new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
-                            context));
+                    new BackAnimationController(shellInit, shellController, shellExecutor,
+                            backgroundHandler, context, transitions));
         }
         return Optional.empty();
     }
@@ -298,17 +294,17 @@
     // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
     @BindsOptionalOf
     @DynamicOverride
-    abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
+    abstract FullscreenTaskListener optionalFullscreenTaskListener();
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener<?> provideFullscreenTaskListener(
-            @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
+    static FullscreenTaskListener provideFullscreenTaskListener(
+            @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
             Optional<RecentTasksController> recentTasksOptional,
-            Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
+            Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
         if (fullscreenTaskListener.isPresent()) {
             return fullscreenTaskListener.get();
         } else {
@@ -322,7 +318,7 @@
     //
 
     @BindsOptionalOf
-    abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+    abstract WindowDecorViewModel optionalWindowDecorViewModel();
 
     //
     // Unfold transition
@@ -477,14 +473,17 @@
     static Optional<RecentTasksController> provideRecentTasksController(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
-                RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, desktopModeTaskRepository, mainExecutor));
+                RecentTasksController.create(context, shellInit, shellController,
+                        shellCommandHandler, taskStackListener, activityTaskManager,
+                        desktopModeTaskRepository, mainExecutor));
     }
 
     //
@@ -501,14 +500,15 @@
     @Provides
     static Transitions provideTransitions(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer organizer,
             TransactionPool pool,
             DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
-                mainHandler, animExecutor);
+        return new Transitions(context, shellInit, shellController, organizer, pool,
+                displayController, mainExecutor, mainHandler, animExecutor);
     }
 
     @WMSingleton
@@ -572,6 +572,47 @@
     }
 
     //
+    // Floating tasks
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<FloatingTasks> provideFloatingTasks(
+            Optional<FloatingTasksController> floatingTaskController) {
+        return floatingTaskController.map((controller) -> controller.asFloatingTasks());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<FloatingTasksController> provideFloatingTasksController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
+            Optional<BubbleController> bubbleController,
+            WindowManager windowManager,
+            ShellTaskOrganizer organizer,
+            TaskViewTransitions taskViewTransitions,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
+            SyncTransactionQueue syncQueue) {
+        if (FloatingTasksController.FLOATING_TASKS_ENABLED) {
+            return Optional.of(new FloatingTasksController(context,
+                    shellInit,
+                    shellController,
+                    shellCommandHandler,
+                    bubbleController,
+                    windowManager,
+                    organizer,
+                    taskViewTransitions,
+                    mainExecutor,
+                    bgExecutor,
+                    syncQueue));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    //
     // Starting window
     //
 
@@ -584,13 +625,15 @@
 
     @WMSingleton
     @Provides
-    static StartingWindowController provideStartingWindowController(Context context,
+    static StartingWindowController provideStartingWindowController(
+            Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             @ShellSplashscreenThread ShellExecutor splashScreenExecutor,
             StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
             TransactionPool pool) {
-        return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+        return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
                 splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
     }
 
@@ -672,15 +715,36 @@
     // Desktop mode (optional feature)
     //
 
+    @WMSingleton
+    @Provides
+    static Optional<DesktopMode> provideDesktopMode(
+            Optional<DesktopModeController> desktopModeController) {
+        return desktopModeController.map(DesktopModeController::asDesktopMode);
+    }
+
+    @BindsOptionalOf
+    @DynamicOverride
+    abstract DesktopModeController optionalDesktopModeController();
+
+    @WMSingleton
+    @Provides
+    static Optional<DesktopModeController> providesDesktopModeController(
+            @DynamicOverride Optional<DesktopModeController> desktopModeController) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
+            return desktopModeController;
+        }
+        return Optional.empty();
+    }
+
     @BindsOptionalOf
     @DynamicOverride
     abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository(
+    static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
             @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
-        if (DesktopMode.IS_SUPPORTED) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             return desktopModeTaskRepository;
         }
         return Optional.empty();
@@ -706,12 +770,11 @@
             DisplayInsetsController displayInsetsController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
-            FullscreenTaskListener<?> fullscreenTaskListener,
+            FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
             Optional<FreeformComponents> freeformComponents,
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 35e88e9..1ec98d3 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
@@ -48,7 +48,6 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -56,7 +55,7 @@
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
 import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -183,20 +182,22 @@
 
     @WMSingleton
     @Provides
-    static WindowDecorViewModel<?> provideWindowDecorViewModel(
+    static WindowDecorViewModel provideWindowDecorViewModel(
             Context context,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            @DynamicOverride DesktopModeController desktopModeController) {
         return new CaptionWindowDecorViewModel(
                         context,
                         mainHandler,
                         mainChoreographer,
                         taskOrganizer,
                         displayController,
-                        syncQueue);
+                        syncQueue,
+                        desktopModeController);
     }
 
     //
@@ -207,7 +208,7 @@
     @Provides
     @DynamicOverride
     static FreeformComponents provideFreeformComponents(
-            FreeformTaskListener<?> taskListener,
+            FreeformTaskListener taskListener,
             FreeformTaskTransitionHandler transitionHandler,
             FreeformTaskTransitionObserver transitionObserver) {
         return new FreeformComponents(
@@ -216,18 +217,18 @@
 
     @WMSingleton
     @Provides
-    static FreeformTaskListener<?> provideFreeformTaskListener(
+    static FreeformTaskListener provideFreeformTaskListener(
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
         ShellInit init = FreeformComponents.isFreeformEnabled(context)
                 ? shellInit
                 : null;
-        return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
+        return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
                 windowDecorViewModel);
     }
 
@@ -236,7 +237,7 @@
     static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
     }
 
@@ -246,10 +247,9 @@
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            FullscreenTaskListener<?> fullscreenTaskListener,
-            FreeformTaskListener<?> freeformTaskListener) {
+            WindowDecorViewModel windowDecorViewModel) {
         return new FreeformTaskTransitionObserver(
-                context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
+                context, shellInit, transitions, windowDecorViewModel);
     }
 
     //
@@ -319,6 +319,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -338,11 +339,12 @@
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(
                 context, shellInit, shellCommandHandler, shellController,
-                displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
-                pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
-                pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
-                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor));
+                displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
+                pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+                phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
+                pipTransitionController, windowManagerShellWrapper, taskStackListener,
+                pipParamsChangedForwarder, displayInsetsController, oneHandedController,
+                mainExecutor));
     }
 
     @WMSingleton
@@ -595,19 +597,20 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeController> provideDesktopModeController(
-            Context context, ShellInit shellInit,
+    @DynamicOverride
+    static DesktopModeController provideDesktopModeController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            Transitions transitions,
+            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread Handler mainHandler,
-            Transitions transitions
+            @ShellMainThread ShellExecutor mainExecutor
     ) {
-        if (DesktopMode.IS_SUPPORTED) {
-            return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
-                    rootTaskDisplayAreaOrganizer, mainHandler, transitions));
-        } else {
-            return Optional.empty();
-        }
+        return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
+                rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
+                mainExecutor);
     }
 
     @WMSingleton
@@ -618,6 +621,28 @@
     }
 
     //
+    // Kids mode
+    //
+    @WMSingleton
+    @Provides
+    static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+            Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            SyncTransactionQueue syncTransactionQueue,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            Optional<UnfoldAnimationController> unfoldAnimationController,
+            Optional<RecentTasksController> recentTasksOptional,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler
+    ) {
+        return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+                syncTransactionQueue, displayController, displayInsetsController,
+                unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
+    }
+
+    //
     // Misc
     //
 
@@ -628,6 +653,7 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DefaultMixedHandler defaultMixedHandler,
+            KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<DesktopModeController> desktopModeController) {
         return new Object();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 8993d54..44a467f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -16,43 +16,11 @@
 
 package com.android.wm.shell.desktopmode;
 
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ExternalThread;
 
 /**
- * Constants for desktop mode feature
+ * Interface to interact with desktop mode feature in shell.
  */
-public class DesktopMode {
-
-    /**
-     * Flag to indicate whether desktop mode is available on the device
-     */
-    public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode", false);
-
-    /**
-     * Check if desktop mode is active
-     *
-     * @return {@code true} if active
-     */
-    public static boolean isActive(Context context) {
-        if (!IS_SUPPORTED) {
-            return false;
-        }
-        try {
-            int result = Settings.System.getIntForUser(context.getContentResolver(),
-                    Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
-            return result != 0;
-        } catch (Exception e) {
-            ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
-            return false;
-        }
-    }
+@ExternalThread
+public interface DesktopMode {
 }
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 6e44d58..b96facf 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
@@ -19,59 +19,117 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArraySet;
+import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.ArrayList;
+import java.util.Comparator;
+
 /**
  * Handles windowing changes when desktop mode system setting changes
  */
-public class DesktopModeController {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+        Transitions.TransitionHandler {
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-    private final SettingsObserver mSettingsObserver;
     private final Transitions mTransitions;
+    private final DesktopModeTaskRepository mDesktopModeTaskRepository;
+    private final ShellExecutor mMainExecutor;
+    private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
+    private final SettingsObserver mSettingsObserver;
 
-    public DesktopModeController(Context context, ShellInit shellInit,
+    public DesktopModeController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            Transitions transitions,
+            DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread Handler mainHandler,
-            Transitions transitions) {
+            @ShellMainThread ShellExecutor mainExecutor) {
         mContext = context;
+        mShellController = shellController;
         mShellTaskOrganizer = shellTaskOrganizer;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
-        mSettingsObserver = new SettingsObserver(mContext, mainHandler);
         mTransitions = transitions;
+        mDesktopModeTaskRepository = desktopModeTaskRepository;
+        mMainExecutor = mainExecutor;
+        mSettingsObserver = new SettingsObserver(mContext, mainHandler);
         shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+                this::createExternalInterface, this);
         mSettingsObserver.observe();
-        if (DesktopMode.isActive(mContext)) {
+        if (DesktopModeStatus.isActive(mContext)) {
             updateDesktopModeActive(true);
         }
+        mTransitions.addHandler(this);
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    /**
+     * Get connection interface between sysui and shell
+     */
+    public DesktopMode asDesktopMode() {
+        return mDesktopModeImpl;
+    }
+
+    /**
+     * Creates a new instance of the external interface to pass to another process.
+     */
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IDesktopModeImpl(this);
     }
 
     @VisibleForTesting
@@ -121,6 +179,81 @@
     }
 
     /**
+     * Show apps on desktop
+     */
+    WindowContainerTransaction showDesktopApps() {
+        ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
+        ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+        for (Integer taskId : activeTasks) {
+            RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
+            if (taskInfo != null) {
+                taskInfos.add(taskInfo);
+            }
+        }
+        // Order by lastActiveTime, descending
+        taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        for (RunningTaskInfo task : taskInfos) {
+            wct.reorder(task.token, true);
+        }
+
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mShellTaskOrganizer.applyTransaction(wct);
+        }
+
+        return wct;
+    }
+
+    /**
+     * Turn desktop mode on or off
+     * @param active the desired state for desktop mode setting
+     */
+    public void setDesktopModeActive(boolean active) {
+        int value = active ? 1 : 0;
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
+    }
+
+    /**
+     * Returns the windowing mode of the display area with the specified displayId.
+     * @param displayId
+     * @return
+     */
+    public int getDisplayAreaWindowingMode(int displayId) {
+        return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+                .configuration.windowConfiguration.getWindowingMode();
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // This handler should never be the sole handler, so should not animate anything.
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+
+        // Only do anything if we are in desktop mode and opening a task/app
+        if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+            return null;
+        }
+
+        WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+        if (wct == null) {
+            wct = new WindowContainerTransaction();
+        }
+        wct.merge(showDesktopApps(), true /* transfer */);
+        wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+        return wct;
+    }
+
+    /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
     private final class SettingsObserver extends ContentObserver {
@@ -150,8 +283,43 @@
         }
 
         private void desktopModeSettingChanged() {
-            boolean enabled = DesktopMode.isActive(mContext);
+            boolean enabled = DesktopModeStatus.isActive(mContext);
             updateDesktopModeActive(enabled);
         }
     }
+
+    /**
+     * The interface for calls from outside the shell, within the host process.
+     */
+    @ExternalThread
+    private final class DesktopModeImpl implements DesktopMode {
+        // Do nothing
+    }
+
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private static class IDesktopModeImpl extends IDesktopMode.Stub
+            implements ExternalInterfaceBinder {
+
+        private DesktopModeController mController;
+
+        IDesktopModeImpl(DesktopModeController controller) {
+            mController = controller;
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        @Override
+        public void invalidate() {
+            mController = null;
+        }
+
+        public void showDesktopApps() {
+            executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
+                    DesktopModeController::showDesktopApps);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
new file mode 100644
index 0000000..195ff50
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeStatus {
+
+    /**
+     * Flag to indicate whether desktop mode is available on the device
+     */
+    public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode", false);
+
+    /**
+     * Check if desktop mode is active
+     *
+     * @return {@code true} if active
+     */
+    public static boolean isActive(Context context) {
+        if (!IS_SUPPORTED) {
+            return false;
+        }
+        try {
+            int result = Settings.System.getIntForUser(context.getContentResolver(),
+                    Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+            return result != 0;
+        } catch (Exception e) {
+            ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
similarity index 72%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 104bf99..5042bd6 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
-
-import android.app.cloudsearch.SearchRequest;
+package com.android.wm.shell.desktopmode;
 
 /**
- * Interface from the system to CloudSearch service.
- *
- * @hide
+ * Interface that is exposed to remote callers to manipulate desktop mode features.
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+interface IDesktopMode {
+
+    /** Show apps on the desktop */
+    void showDesktopApps();
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d..fbf326e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@
 ### SysUI accessible components
 In addition to doing the above, you will also need to provide an interface for calling to SysUI
 from the Shell and vice versa.  The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
 
 In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
 add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
 `SysUIComponent` to take the interface so it can be injected in SysUI code.  The binding between
 the two is done in `SystemUIFactory#init()` which will need to be updated as well.
 
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+  controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
 ### Launcher accessible components
 Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
 Launcher requires a new AIDL interface to be created and implemented by the controller.  The
 implementation of the stub interface in the controller otherwise behaves similar to the interface
 to SysUI where it posts the work to the main Shell thread.
 
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+  extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+  references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+  the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+  and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+  call the SystemUIProxy method as needed in that code
+
 ### Component initialization
 To initialize the component:
 - On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@
 
 ### General Do's & Dont's
 Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
 
 Don't:
 - **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
new file mode 100644
index 0000000..83a1734
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.floating.views.FloatingTaskLayer;
+import com.android.wm.shell.floating.views.FloatingTaskView;
+
+import java.util.Objects;
+
+/**
+ * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved
+ * close to the target to be stuck to it unless moved out again.
+ */
+public class FloatingDismissController {
+
+    /** Velocity required to dismiss the view without dragging it into the dismiss target. */
+    private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
+    /**
+     * Max velocity that the view can be moving through the target with to stick (i.e. if it's
+     * more than this velocity, it will pass through the target.
+     */
+    private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f;
+    /**
+     * Percentage of the target width to use to determine if an object flung towards the target
+     * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a
+     * 200px-wide area around the target will be considered 'near' enough get dismissed).
+     */
+    private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f;
+    /** Minimum alpha to apply to the view being dismissed when it is in the target. */
+    private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f;
+    /** Amount to scale down the view being dismissed when it is in the target. */
+    private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f;
+
+    private Context mContext;
+    private FloatingTasksController mController;
+    private FloatingTaskLayer mParent;
+
+    private DismissView mDismissView;
+    private ValueAnimator mDismissAnimator;
+    private View mViewBeingDismissed;
+    private float mDismissSizePercent;
+    private float mDismissSize;
+
+    /**
+     * The currently magnetized object, which is being dragged and will be attracted to the magnetic
+     * dismiss target.
+     */
+    private MagnetizedObject<View> mMagnetizedObject;
+    /**
+     * The MagneticTarget instance for our circular dismiss view. This is added to the
+     * MagnetizedObject instances for the view being dragged.
+     */
+    private MagnetizedObject.MagneticTarget mMagneticTarget;
+    /** Magnet listener that handles animating and dismissing the view. */
+    private MagnetizedObject.MagnetListener mFloatingViewMagnetListener;
+
+    public FloatingDismissController(Context context, FloatingTasksController controller,
+            FloatingTaskLayer parent) {
+        mContext = context;
+        mController = controller;
+        mParent = parent;
+        updateSizes();
+        createAndAddDismissView();
+
+        mDismissAnimator = ValueAnimator.ofFloat(1f, 0f);
+        mDismissAnimator.addUpdateListener(animation -> {
+            final float value = (float) animation.getAnimatedValue();
+            if (mDismissView != null) {
+                mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
+                mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
+                final float scaleValue = Math.max(value, mDismissSizePercent);
+                mDismissView.getCircle().setScaleX(scaleValue);
+                mDismissView.getCircle().setScaleY(scaleValue);
+            }
+            if (mViewBeingDismissed != null) {
+                // TODO: alpha doesn't actually apply to taskView currently.
+                mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA));
+                mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
+                mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
+            }
+        });
+
+        mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(
+                    @NonNull MagnetizedObject.MagneticTarget target) {
+                animateDismissing(/* dismissing= */ true);
+            }
+
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    float velX, float velY, boolean wasFlungOut) {
+                animateDismissing(/* dismissing= */ false);
+                mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY,
+                        wasFlungOut);
+            }
+
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                doDismiss();
+            }
+        };
+    }
+
+    /** Updates all the sizes used and applies them to the {@link DismissView}. */
+    public void updateSizes() {
+        Resources res = mContext.getResources();
+        mDismissSize = res.getDimensionPixelSize(
+                R.dimen.floating_task_dismiss_circle_size);
+        final float minDismissSize = res.getDimensionPixelSize(
+                R.dimen.floating_dismiss_circle_small);
+        mDismissSizePercent = minDismissSize / mDismissSize;
+
+        if (mDismissView != null) {
+            mDismissView.updateResources();
+        }
+    }
+
+    /** Prepares the view being dragged to be magnetic. */
+    public void setUpMagneticObject(View viewBeingDragged) {
+        mViewBeingDismissed = viewBeingDragged;
+        mMagnetizedObject = getMagnetizedView(viewBeingDragged);
+        mMagnetizedObject.clearAllTargets();
+        mMagnetizedObject.addTarget(mMagneticTarget);
+        mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener);
+    }
+
+    /** Shows or hides the dismiss target. */
+    public void showDismiss(boolean show) {
+        if (show) {
+            mDismissView.show();
+        } else {
+            mDismissView.hide();
+        }
+    }
+
+    /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
+    public boolean passEventToMagnetizedObject(MotionEvent event) {
+        return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
+    }
+
+    private void createAndAddDismissView() {
+        if (mDismissView != null) {
+            mParent.removeView(mDismissView);
+        }
+        mDismissView = new DismissView(mContext);
+        mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size);
+        mDismissView.updateResources();
+        mParent.addView(mDismissView);
+
+        final float dismissRadius = mDismissSize;
+        // Save the MagneticTarget instance for the newly set up view - we'll add this to the
+        // MagnetizedObjects when the dismiss view gets shown.
+        mMagneticTarget = new MagnetizedObject.MagneticTarget(
+                mDismissView.getCircle(), (int) dismissRadius);
+    }
+
+    private MagnetizedObject<View> getMagnetizedView(View v) {
+        if (mMagnetizedObject != null
+                && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) {
+            // Same view being dragged, we can reuse the magnetic object.
+            return mMagnetizedObject;
+        }
+        MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>(
+                mContext,
+                v,
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y
+        ) {
+            @Override
+            public float getWidth(@NonNull View underlyingObject) {
+                return underlyingObject.getWidth();
+            }
+
+            @Override
+            public float getHeight(@NonNull View underlyingObject) {
+                return underlyingObject.getHeight();
+            }
+
+            @Override
+            public void getLocationOnScreen(@NonNull View underlyingObject,
+                    @NonNull int[] loc) {
+                loc[0] = (int) underlyingObject.getTranslationX();
+                loc[1] = (int) underlyingObject.getTranslationY();
+            }
+        };
+        magnetizedView.setHapticsEnabled(true);
+        magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+        magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY);
+        magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT);
+        return magnetizedView;
+    }
+
+    /** Animates the dismiss treatment on the view being dismissed. */
+    private void animateDismissing(boolean shouldDismiss) {
+        if (mViewBeingDismissed == null) {
+            return;
+        }
+        if (shouldDismiss) {
+            mDismissAnimator.removeAllListeners();
+            mDismissAnimator.start();
+        } else {
+            mDismissAnimator.removeAllListeners();
+            mDismissAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    resetDismissAnimator();
+                }
+            });
+            mDismissAnimator.reverse();
+        }
+    }
+
+    /** Actually dismisses the view. */
+    private void doDismiss() {
+        mDismissView.hide();
+        mController.removeTask();
+        resetDismissAnimator();
+        mViewBeingDismissed = null;
+    }
+
+    private void resetDismissAnimator() {
+        mDismissAnimator.removeAllListeners();
+        mDismissAnimator.cancel();
+        if (mDismissView != null) {
+            mDismissView.cancelAnimators();
+            mDismissView.getCircle().setScaleX(1f);
+            mDismissView.getCircle().setScaleY(1f);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
new file mode 100644
index 0000000..f86d467
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating;
+
+import android.content.Intent;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface to interact with floating tasks.
+ */
+@ExternalThread
+public interface FloatingTasks {
+
+    /**
+     * Shows, stashes, or un-stashes the floating task depending on state:
+     * - If there is no floating task for this intent, it shows the task for the provided intent.
+     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
+     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
+     */
+    void showOrSetStashed(Intent intent);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
new file mode 100644
index 0000000..b3c09d3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.SystemProperties;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.annotations.ShellBackgroundThread;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.floating.views.FloatingTaskLayer;
+import com.android.wm.shell.floating.views.FloatingTaskView;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Entry point for creating and managing floating tasks.
+ *
+ * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView}
+ * within that window.
+ *
+ * Currently optimized for a single task. Multiple tasks are not supported.
+ */
+public class FloatingTasksController implements RemoteCallable<FloatingTasksController>,
+        ConfigurationChangeListener {
+
+    private static final String TAG = FloatingTasksController.class.getSimpleName();
+
+    public static final boolean FLOATING_TASKS_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
+    public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES =
+            SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false);
+
+    @VisibleForTesting
+    static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600;
+
+    // Only used for testing
+    private Configuration mConfig;
+    private boolean mFloatingTasksEnabledForTests;
+
+    private FloatingTaskImpl mImpl = new FloatingTaskImpl();
+    private Context mContext;
+    private ShellController mShellController;
+    private ShellCommandHandler mShellCommandHandler;
+    private @Nullable BubbleController mBubbleController;
+    private WindowManager mWindowManager;
+    private ShellTaskOrganizer mTaskOrganizer;
+    private TaskViewTransitions mTaskViewTransitions;
+    private @ShellMainThread ShellExecutor mMainExecutor;
+    // TODO: mBackgroundThread is not used but we'll probs need it eventually?
+    private @ShellBackgroundThread ShellExecutor mBackgroundThread;
+    private SyncTransactionQueue mSyncQueue;
+
+    private boolean mIsFloatingLayerAdded;
+    private FloatingTaskLayer mFloatingTaskLayer;
+    private final Point mLastPosition = new Point(-1, -1);
+
+    private Task mTask;
+
+    // Simple class to hold onto info for intent or shortcut based tasks.
+    public static class Task {
+        public int taskId = INVALID_TASK_ID;
+        @Nullable
+        public Intent intent;
+        @Nullable
+        public ShortcutInfo info;
+        @Nullable
+        public FloatingTaskView floatingView;
+    }
+
+    public FloatingTasksController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
+            Optional<BubbleController> bubbleController,
+            WindowManager windowManager,
+            ShellTaskOrganizer organizer,
+            TaskViewTransitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellBackgroundThread ShellExecutor bgExceutor,
+            SyncTransactionQueue syncTransactionQueue) {
+        mContext = context;
+        mShellController = shellController;
+        mShellCommandHandler = shellCommandHandler;
+        mBubbleController = bubbleController.get();
+        mWindowManager = windowManager;
+        mTaskOrganizer = organizer;
+        mTaskViewTransitions = transitions;
+        mMainExecutor = mainExecutor;
+        mBackgroundThread = bgExceutor;
+        mSyncQueue = syncTransactionQueue;
+        if (isFloatingTasksEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    protected void onInit() {
+        mShellController.addConfigurationChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+                this::createExternalInterface, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
+    }
+
+    /** Only used for testing. */
+    @VisibleForTesting
+    void setConfig(Configuration config) {
+        mConfig = config;
+    }
+
+    /** Only used for testing. */
+    @VisibleForTesting
+    void setFloatingTasksEnabled(boolean enabled) {
+        mFloatingTasksEnabledForTests = enabled;
+    }
+
+    /** Whether the floating layer is available. */
+    boolean isFloatingLayerAvailable() {
+        Configuration config = mConfig == null
+                ? mContext.getResources().getConfiguration()
+                : mConfig;
+        return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
+    }
+
+    /** Whether floating tasks are enabled.  */
+    boolean isFloatingTasksEnabled() {
+        return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
+    }
+
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IFloatingTasksImpl(this);
+    }
+
+    @Override
+    public void onThemeChanged() {
+        if (mIsFloatingLayerAdded) {
+            mFloatingTaskLayer.updateSizes();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // TODO: probably other stuff here to do (e.g. handle rotation)
+        if (mIsFloatingLayerAdded) {
+            mFloatingTaskLayer.updateSizes();
+        }
+    }
+
+    /** Returns false if the task shouldn't be shown. */
+    private boolean canShowTask(Intent intent) {
+        ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask --  %s", intent);
+        if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false;
+        if (intent == null) {
+            ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing");
+            return false;
+        }
+        return true;
+    }
+
+    /** Returns true if the task was or should be shown as a bubble. */
+    private boolean maybeShowTaskAsBubble(Intent intent) {
+        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) {
+            removeFloatingLayer();
+            if (intent.getPackage() != null) {
+                mBubbleController.addAppBubble(intent);
+                ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent);
+            } else {
+                ProtoLog.d(WM_SHELL_FLOATING_APPS,
+                        "failed to show floating task as bubble: %s; unknown package", intent);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Shows, stashes, or un-stashes the floating task depending on state:
+     * - If there is no floating task for this intent, it shows this the provided task.
+     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
+     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
+     */
+    public void showOrSetStashed(Intent intent) {
+        if (!canShowTask(intent)) return;
+        if (maybeShowTaskAsBubble(intent)) return;
+
+        addFloatingLayer();
+
+        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
+            // The task is already added, toggle the stash state.
+            mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed());
+            return;
+        }
+
+        // If we're here it's either a new or different task
+        showNewTask(intent);
+    }
+
+    /**
+     * Shows a floating task with the provided intent.
+     * If the same task is present it will un-stash it or do nothing if it is already un-stashed.
+     * Removes any other floating tasks that might exist.
+     */
+    public void showTask(Intent intent) {
+        if (!canShowTask(intent)) return;
+        if (maybeShowTaskAsBubble(intent)) return;
+
+        addFloatingLayer();
+
+        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
+            // The task is already added, show it if it's stashed.
+            if (mTask.floatingView.isStashed()) {
+                mFloatingTaskLayer.setStashed(mTask, false);
+            }
+            return;
+        }
+        showNewTask(intent);
+    }
+
+    private void showNewTask(Intent intent) {
+        if (mTask != null && !intent.filterEquals(mTask.intent)) {
+            mFloatingTaskLayer.removeAllTaskViews();
+            mTask.floatingView.cleanUpTaskView();
+            mTask = null;
+        }
+
+        FloatingTaskView ftv = new FloatingTaskView(mContext, this);
+        ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
+
+        mTask = new Task();
+        mTask.floatingView = ftv;
+        mTask.intent = intent;
+
+        // Add & start the task.
+        mFloatingTaskLayer.addTask(mTask);
+        ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent);
+        mTask.floatingView.startTask(mMainExecutor, mTask);
+    }
+
+    /**
+     * Removes the task and cleans up the view.
+     */
+    public void removeTask() {
+        if (mTask != null) {
+            ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId);
+
+            if (mTask.floatingView != null) {
+                // TODO: animate it
+                mFloatingTaskLayer.removeView(mTask.floatingView);
+                mTask.floatingView.cleanUpTaskView();
+            }
+            removeFloatingLayer();
+        }
+    }
+
+    /**
+     * Whether there is a floating task and if it is stashed.
+     */
+    public boolean isStashed() {
+        return isTaskAttached(mTask) && mTask.floatingView.isStashed();
+    }
+
+    /**
+     * If a floating task exists, this sets whether it is stashed and animates if needed.
+     */
+    public void setStashed(boolean shouldStash) {
+        if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) {
+            mFloatingTaskLayer.setStashed(mTask, shouldStash);
+        }
+    }
+
+    /**
+     * Saves the last position the floating task was in so that it can be put there again.
+     */
+    public void setLastPosition(int x, int y) {
+        mLastPosition.set(x, y);
+    }
+
+    /**
+     * Returns the last position the floating task was in.
+     */
+    public Point getLastPosition() {
+        return mLastPosition;
+    }
+
+    /**
+     * Whether the provided task has a view that's attached to the floating layer.
+     */
+    private boolean isTaskAttached(Task t) {
+        return t != null && t.floatingView != null
+                && mIsFloatingLayerAdded
+                && mFloatingTaskLayer.getTaskViewCount() > 0
+                && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView);
+    }
+
+    // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this
+    //  type allowed? Bubbles & floating tasks should probably be in the same layer to reduce
+    //  # of windows.
+    private void addFloatingLayer() {
+        if (mIsFloatingLayerAdded) {
+            return;
+        }
+
+        mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager);
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT
+        );
+        params.setTrustedOverlay();
+        params.setFitInsetsTypes(0);
+        params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+        params.setTitle("FloatingTaskLayer");
+        params.packageName = mContext.getPackageName();
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
+        try {
+            mIsFloatingLayerAdded = true;
+            mWindowManager.addView(mFloatingTaskLayer, params);
+        } catch (IllegalStateException e) {
+            // This means the floating layer has already been added which shouldn't happen.
+            e.printStackTrace();
+        }
+    }
+
+    private void removeFloatingLayer() {
+        if (!mIsFloatingLayerAdded) {
+            return;
+        }
+        try {
+            mIsFloatingLayerAdded = false;
+            if (mFloatingTaskLayer != null) {
+                mWindowManager.removeView(mFloatingTaskLayer);
+            }
+        } catch (IllegalArgumentException e) {
+            // This means the floating layer has already been removed which shouldn't happen.
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Description of current floating task state.
+     */
+    private void dump(PrintWriter pw, String prefix) {
+        pw.println("FloatingTaskController state:");
+        pw.print("   isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable());
+        pw.print("   isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled());
+        pw.print("   mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded);
+        pw.print("   mLastPosition= "); pw.println(mLastPosition);
+        pw.println();
+    }
+
+    /** Returns the {@link FloatingTasks} implementation. */
+    public FloatingTasks asFloatingTasks() {
+        return mImpl;
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    /**
+     * The interface for calls from outside the shell, within the host process.
+     */
+    @ExternalThread
+    private class FloatingTaskImpl implements FloatingTasks {
+        @Override
+        public void showOrSetStashed(Intent intent) {
+            mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
+        }
+    }
+
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+            implements ExternalInterfaceBinder {
+        private FloatingTasksController mController;
+
+        IFloatingTasksImpl(FloatingTasksController controller) {
+            mController = controller;
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        @Override
+        public void invalidate() {
+            mController = null;
+        }
+
+        public void showTask(Intent intent) {
+            executeRemoteCallWithTaskPermission(mController, "showTask",
+                    (controller) ->  controller.showTask(intent));
+        }
+    }
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
similarity index 72%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
index 104bf99..f79ca10 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
+package com.android.wm.shell.floating;
 
-import android.app.cloudsearch.SearchRequest;
+import android.content.Intent;
 
 /**
- * Interface from the system to CloudSearch service.
- *
- * @hide
+ * Interface that is exposed to remote callers to manipulate floating task features.
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
+interface IFloatingTasks {
+
+    void showTask(in Intent intent) = 1;
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
new file mode 100644
index 0000000..c922109
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating.views;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Displays the menu items for a floating task view (e.g. close).
+ */
+public class FloatingMenuView extends LinearLayout {
+
+    private int mItemSize;
+    private int mItemMargin;
+
+    public FloatingMenuView(Context context) {
+        super(context);
+        setOrientation(LinearLayout.HORIZONTAL);
+        setGravity(Gravity.CENTER);
+
+        mItemSize = context.getResources().getDimensionPixelSize(
+                R.dimen.floating_task_menu_item_size);
+        mItemMargin = context.getResources().getDimensionPixelSize(
+                R.dimen.floating_task_menu_item_padding);
+    }
+
+    /** Adds a clickable item to the menu bar. Items are ordered as added. */
+    public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) {
+        ImageView itemView = new ImageView(getContext());
+        itemView.setScaleType(ImageView.ScaleType.CENTER);
+        if (drawable != null) {
+            itemView.setImageDrawable(drawable);
+        }
+        LinearLayout.LayoutParams lp = new LayoutParams(mItemSize,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        lp.setMarginStart(mItemMargin);
+        lp.setMarginEnd(mItemMargin);
+        addView(itemView, lp);
+
+        itemView.setOnClickListener(listener);
+    }
+
+    /**
+     * 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/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
new file mode 100644
index 0000000..16dab24
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating.views;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FlingAnimation;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.floating.FloatingDismissController;
+import com.android.wm.shell.floating.FloatingTasksController;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and
+ * movement of the task views.
+ */
+public class FloatingTaskLayer extends FrameLayout
+        implements ViewTreeObserver.OnComputeInternalInsetsListener {
+
+    private static final String TAG = FloatingTaskLayer.class.getSimpleName();
+
+    /** How big to make the task view based on screen width of the largest size. */
+    private static final float START_SIZE_WIDTH_PERCENT = 0.33f;
+    /** Min fling velocity required to move the view from one side of the screen to the other. */
+    private static final float ESCAPE_VELOCITY = 750f;
+    /** Amount of friction to apply to fling animations. */
+    private static final float FLING_FRICTION = 1.9f;
+
+    private final FloatingTasksController mController;
+    private final FloatingDismissController mDismissController;
+    private final WindowManager mWindowManager;
+    private final TouchHandlerImpl mTouchHandler;
+
+    private final Region mTouchableRegion = new Region();
+    private final Rect mPositionRect = new Rect();
+    private final Point mDefaultStartPosition = new Point();
+    private final Point mTaskViewSize = new Point();
+    private WindowInsets mWindowInsets;
+    private int mVerticalPadding;
+    private int mOverhangWhenStashed;
+
+    private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
+    private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener =
+            this::updateSystemGestureExclusion;
+
+    /** Interface allowing something to handle the touch events going to a task. */
+    interface FloatingTaskTouchHandler {
+        void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
+                float viewInitialX, float viewInitialY);
+
+        void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
+                 float dx, float dy);
+
+        void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
+                float dx, float dy, float velX, float velY);
+
+        void onClick(@NonNull FloatingTaskView v);
+    }
+
+    public FloatingTaskLayer(Context context,
+            FloatingTasksController controller,
+            WindowManager windowManager) {
+        super(context);
+        // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly.
+        setBackgroundColor(Color.argb(0, 0, 0, 0));
+
+        mController = controller;
+        mWindowManager = windowManager;
+        updateSizes();
+
+        // TODO: Might make sense to put dismiss controller in the touch handler since that's the
+        //  main user of dismiss controller.
+        mDismissController = new FloatingDismissController(context, mController, this);
+        mTouchHandler = new TouchHandlerImpl();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+        getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener);
+        setOnApplyWindowInsetsListener((view, windowInsets) -> {
+            if (!windowInsets.equals(mWindowInsets)) {
+                mWindowInsets = windowInsets;
+                updateSizes();
+            }
+            return windowInsets;
+        });
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener);
+    }
+
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        mTouchableRegion.setEmpty();
+        getTouchableRegion(mTouchableRegion);
+        inoutInfo.touchableRegion.set(mTouchableRegion);
+    }
+
+    /** Adds a floating task to the layout. */
+    public void addTask(FloatingTasksController.Task task) {
+        if (task.floatingView == null) return;
+
+        task.floatingView.setTouchHandler(mTouchHandler);
+        addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y));
+        updateTaskViewPosition(task.floatingView);
+    }
+
+    /** Animates the stashed state of the provided task, if it's part of the floating layer. */
+    public void setStashed(FloatingTasksController.Task task, boolean shouldStash) {
+        if (task.floatingView != null && task.floatingView.getParent() == this) {
+            mTouchHandler.stashTaskView(task.floatingView, shouldStash);
+        }
+    }
+
+    /** Removes all {@link FloatingTaskView} from the layout. */
+    public void removeAllTaskViews() {
+        int childCount = getChildCount();
+        ArrayList<View> viewsToRemove = new ArrayList<>();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i) instanceof FloatingTaskView) {
+                viewsToRemove.add(getChildAt(i));
+            }
+        }
+        for (View v : viewsToRemove) {
+            removeView(v);
+        }
+    }
+
+    /** Returns the number of task views in the layout. */
+    public int getTaskViewCount() {
+        int taskViewCount = 0;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i) instanceof FloatingTaskView) {
+                taskViewCount++;
+            }
+        }
+        return taskViewCount;
+    }
+
+    /**
+     * Called when the task view is un-stuck from the dismiss target.
+     * @param v the task view being moved.
+     * @param velX the x velocity of the motion event.
+     * @param velY the y velocity of the motion event.
+     * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there
+     *                    was an 'up' event), otherwise the user is still dragging.
+     */
+    public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
+            boolean wasFlungOut) {
+        mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut);
+    }
+
+    /**
+     * Updates dimensions and applies them to any task views.
+     */
+    public void updateSizes() {
+        if (mDismissController != null) {
+            mDismissController.updateSizes();
+        }
+
+        mOverhangWhenStashed = getResources().getDimensionPixelSize(
+                R.dimen.floating_task_stash_offset);
+        mVerticalPadding = getResources().getDimensionPixelSize(
+                R.dimen.floating_task_vertical_padding);
+
+        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        WindowInsets windowInsets = windowMetrics.getWindowInsets();
+        Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+                | WindowInsets.Type.statusBars()
+                | WindowInsets.Type.displayCutout());
+        Rect bounds = windowMetrics.getBounds();
+        mPositionRect.set(bounds.left + insets.left,
+                bounds.top + insets.top + mVerticalPadding,
+                bounds.right - insets.right,
+                bounds.bottom - insets.bottom - mVerticalPadding);
+
+        int taskViewWidth = Math.max(bounds.height(), bounds.width());
+        int taskViewHeight = Math.min(bounds.height(), bounds.width());
+        taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2));
+        mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight);
+        mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top);
+
+        // Update existing views
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i) instanceof FloatingTaskView) {
+                FloatingTaskView child = (FloatingTaskView) getChildAt(i);
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                lp.width = mTaskViewSize.x;
+                lp.height = mTaskViewSize.y;
+                child.setLayoutParams(lp);
+                updateTaskViewPosition(child);
+            }
+        }
+    }
+
+    /** Returns the first floating task view in the layout. (Currently only ever 1 view). */
+    @Nullable
+    public FloatingTaskView getFirstTaskView() {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child instanceof FloatingTaskView) {
+                return (FloatingTaskView) child;
+            }
+        }
+        return null;
+    }
+
+    private void updateTaskViewPosition(FloatingTaskView floatingView) {
+        Point lastPosition = mController.getLastPosition();
+        if (lastPosition.x == -1 && lastPosition.y == -1) {
+            floatingView.setX(mDefaultStartPosition.x);
+            floatingView.setY(mDefaultStartPosition.y);
+        } else {
+            floatingView.setX(lastPosition.x);
+            floatingView.setY(lastPosition.y);
+        }
+        if (mTouchHandler.isStashedPosition(floatingView)) {
+            floatingView.setStashed(true);
+        }
+        floatingView.updateLocation();
+    }
+
+    /**
+     * Updates the area of the screen that shouldn't allow the back gesture due to the placement
+     * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would
+     * un-stash the task view instead of performing the back gesture).
+     */
+    private void updateSystemGestureExclusion() {
+        Rect excludeZone = mSystemGestureExclusionRects.get(0);
+        FloatingTaskView floatingTaskView = getFirstTaskView();
+        if (floatingTaskView != null && floatingTaskView.isStashed()) {
+            excludeZone.set(floatingTaskView.getLeft(),
+                    floatingTaskView.getTop(),
+                    floatingTaskView.getRight(),
+                    floatingTaskView.getBottom());
+            excludeZone.offset((int) (floatingTaskView.getTranslationX()),
+                    (int) (floatingTaskView.getTranslationY()));
+            setSystemGestureExclusionRects(mSystemGestureExclusionRects);
+        } else {
+            excludeZone.setEmpty();
+            setSystemGestureExclusionRects(Collections.emptyList());
+        }
+    }
+
+    /**
+     * Fills in the touchable region for floating windows. This is used by WindowManager to
+     * decide which touch events go to the floating windows.
+     */
+    private void getTouchableRegion(Region outRegion) {
+        int childCount = getChildCount();
+        Rect temp = new Rect();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child instanceof FloatingTaskView) {
+                child.getBoundsOnScreen(temp);
+                outRegion.op(temp, Region.Op.UNION);
+            }
+        }
+    }
+
+    /**
+     * Implementation of the touch handler. Animates the task view based on touch events.
+     */
+    private class TouchHandlerImpl implements FloatingTaskTouchHandler {
+        /**
+         * The view can be stashed by swiping it towards the current edge or moving it there. If
+         * the view gets moved in a way that is not one of these gestures, this is flipped to false.
+         */
+        private boolean mCanStash = true;
+        /**
+         * This is used to indicate that the view has been un-stuck from the dismiss target and
+         * needs to spring to the current touch location.
+         */
+        // TODO: implement this behavior
+        private boolean mSpringToTouchOnNextMotionEvent = false;
+
+        private ArrayList<FlingAnimation> mFlingAnimations;
+        private ViewPropertyAnimator mViewPropertyAnimation;
+
+        private float mViewInitialX;
+        private float mViewInitialY;
+
+        private float[] mMinMax = new float[2];
+
+        @Override
+        public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY) {
+            mCanStash = true;
+            mViewInitialX = viewInitialX;
+            mViewInitialY = viewInitialY;
+            mDismissController.setUpMagneticObject(v);
+            mDismissController.passEventToMagnetizedObject(ev);
+        }
+
+        @Override
+        public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
+                float dx, float dy) {
+            // Shows the magnetic dismiss target if needed.
+            mDismissController.showDismiss(/* show= */ true);
+
+            // Send it to magnetic target first.
+            if (mDismissController.passEventToMagnetizedObject(ev)) {
+                v.setStashed(false);
+                mCanStash = true;
+
+                return;
+            }
+
+            // If we're here magnetic target didn't want it so move as per normal.
+
+            v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true));
+            v.setTranslationY(capY(v, mViewInitialY + dy));
+            if (v.isStashed()) {
+                // Check if we've moved far enough to be not stashed.
+                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
+                final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX;
+                if (viewInitiallyOnLeftSide) {
+                    if (v.getTranslationX() > mPositionRect.left) {
+                        v.setStashed(false);
+                        mCanStash = true;
+                    }
+                } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) {
+                    v.setStashed(false);
+                    mCanStash = true;
+                }
+            }
+        }
+
+        // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge.
+        // TODO clean up the code here, pretty hard to comprehend
+        // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges)
+        @Override
+        public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
+                float dx, float dy, float velX, float velY) {
+
+            // Send it to magnetic target first.
+            if (mDismissController.passEventToMagnetizedObject(ev)) {
+                v.setStashed(false);
+                return;
+            }
+            mDismissController.showDismiss(/* show= */ false);
+
+            // If we're here magnetic target didn't want it so handle up as per normal.
+
+            final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false);
+            final float centerX = mPositionRect.centerX();
+            final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX;
+            final boolean viewOnLeftSide = x + v.getWidth() < centerX;
+            final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY;
+            final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY;
+            // TODO: check velX here sometimes it doesn't stash on move when I think it should
+            final boolean shouldStashFromMove =
+                    (velX < 0 && v.getTranslationX() < mPositionRect.left)
+                            || (velX > 0
+                            && v.getTranslationX() + v.getWidth() > mPositionRect.right);
+            final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide
+                    && isFling
+                    && ((viewOnLeftSide && velX < ESCAPE_VELOCITY)
+                    || (!viewOnLeftSide && velX > ESCAPE_VELOCITY));
+            final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove);
+
+            ProtoLog.d(WM_SHELL_FLOATING_APPS,
+                    "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s"
+                    + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f"
+                    + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove,
+                    viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed());
+
+            if (v.isStashed()) {
+                mMinMax[0] = viewOnLeftSide
+                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
+                        : mPositionRect.right - v.getWidth();
+                mMinMax[1] = viewOnLeftSide
+                        ? mPositionRect.left
+                        : mPositionRect.right - mOverhangWhenStashed;
+            } else {
+                populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax);
+            }
+
+            boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide;
+            float destinationRelativeX = movingLeft
+                    ? mMinMax[0]
+                    : mMinMax[1];
+
+            // TODO: why is this necessary / when does this happen?
+            if (mMinMax[1] < v.getTranslationX()) {
+                mMinMax[1] = v.getTranslationX();
+            }
+            if (v.getTranslationX() < mMinMax[0]) {
+                mMinMax[0] = v.getTranslationX();
+            }
+
+            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
+            // so that it'll make it all the way to the side of the screen.
+            final float minimumVelocityToReachEdge =
+                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
+            final float startXVelocity = movingLeft
+                    ? Math.min(minimumVelocityToReachEdge, velX)
+                    : Math.max(minimumVelocityToReachEdge, velX);
+
+            cancelAnyAnimations(v);
+
+            mFlingAnimations = getAnimationForUpEvent(v, shouldStash,
+                    startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX);
+            for (int i = 0; i < mFlingAnimations.size(); i++) {
+                mFlingAnimations.get(i).start();
+            }
+        }
+
+        @Override
+        public void onClick(@NonNull FloatingTaskView v) {
+            if (v.isStashed()) {
+                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
+                final boolean viewOnLeftSide = v.getTranslationX() < centerX;
+                final float destinationRelativeX = viewOnLeftSide
+                        ? mPositionRect.left
+                        : mPositionRect.right - v.getWidth();
+                final float minimumVelocityToReachEdge =
+                        getMinimumVelocityToReachEdge(v, destinationRelativeX);
+                populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax);
+
+                cancelAnyAnimations(v);
+
+                FlingAnimation flingAnimation = new FlingAnimation(v,
+                        DynamicAnimation.TRANSLATION_X);
+                flingAnimation.setFriction(FLING_FRICTION)
+                        .setStartVelocity(minimumVelocityToReachEdge)
+                        .setMinValue(mMinMax[0])
+                        .setMaxValue(mMinMax[1])
+                        .addEndListener((animation, canceled, value, velocity) -> {
+                            if (canceled) return;
+                            mController.setLastPosition((int) v.getTranslationX(),
+                                    (int) v.getTranslationY());
+                            v.setStashed(false);
+                            v.updateLocation();
+                        });
+                mFlingAnimations = new ArrayList<>();
+                mFlingAnimations.add(flingAnimation);
+                flingAnimation.start();
+            }
+        }
+
+        public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
+                boolean wasFlungOut) {
+            if (wasFlungOut) {
+                snapTaskViewToEdge(v, velX, /* shouldStash= */ false);
+            } else {
+                // TODO: use this for something / to spring the view to the touch location
+                mSpringToTouchOnNextMotionEvent = true;
+            }
+        }
+
+        public void stashTaskView(FloatingTaskView v, boolean shouldStash) {
+            if (v.isStashed() == shouldStash) {
+                return;
+            }
+            final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
+            final boolean viewOnLeftSide = v.getTranslationX() < centerX;
+            snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash);
+        }
+
+        public boolean isStashedPosition(View v) {
+            return v.getTranslationX() < mPositionRect.left
+                    || v.getTranslationX() + v.getWidth() > mPositionRect.right;
+        }
+
+        // TODO: a lot of this is duplicated in onUp -- can it be unified?
+        private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) {
+            final boolean movingLeft = velX < ESCAPE_VELOCITY;
+            populateMinMax(v, movingLeft, shouldStash, mMinMax);
+            float destinationRelativeX = movingLeft
+                    ? mMinMax[0]
+                    : mMinMax[1];
+
+            // TODO: why is this necessary / when does this happen?
+            if (mMinMax[1] < v.getTranslationX()) {
+                mMinMax[1] = v.getTranslationX();
+            }
+            if (v.getTranslationX() < mMinMax[0]) {
+                mMinMax[0] = v.getTranslationX();
+            }
+
+            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
+            // so that it'll make it all the way to the side of the screen.
+            final float minimumVelocityToReachEdge =
+                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
+            final float startXVelocity = movingLeft
+                    ? Math.min(minimumVelocityToReachEdge, velX)
+                    : Math.max(minimumVelocityToReachEdge, velX);
+
+            cancelAnyAnimations(v);
+
+            mFlingAnimations = getAnimationForUpEvent(v,
+                    shouldStash, startXVelocity,  mMinMax[0], mMinMax[1],
+                    destinationRelativeX);
+            for (int i = 0; i < mFlingAnimations.size(); i++) {
+                mFlingAnimations.get(i).start();
+            }
+        }
+
+        private void cancelAnyAnimations(FloatingTaskView v) {
+            if (mFlingAnimations != null) {
+                for (int i = 0; i < mFlingAnimations.size(); i++) {
+                    if (mFlingAnimations.get(i).isRunning()) {
+                        mFlingAnimations.get(i).cancel();
+                    }
+                }
+            }
+            if (mViewPropertyAnimation != null) {
+                mViewPropertyAnimation.cancel();
+                mViewPropertyAnimation = null;
+            }
+        }
+
+        private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v,
+                boolean shouldStash, float startVelX, float minValue, float maxValue,
+                float destinationRelativeX) {
+            final float ty = v.getTranslationY();
+            final ArrayList<FlingAnimation> animations = new ArrayList<>();
+            if (ty != capY(v, ty)) {
+                // The view was being dismissed so the Y is out of bounds, need to animate that.
+                FlingAnimation yFlingAnimation = new FlingAnimation(v,
+                        DynamicAnimation.TRANSLATION_Y);
+                yFlingAnimation.setFriction(FLING_FRICTION)
+                        .setStartVelocity(startVelX)
+                        .setMinValue(mPositionRect.top)
+                        .setMaxValue(mPositionRect.bottom - mTaskViewSize.y);
+                animations.add(yFlingAnimation);
+            }
+            FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X);
+            flingAnimation.setFriction(FLING_FRICTION)
+                    .setStartVelocity(startVelX)
+                    .setMinValue(minValue)
+                    .setMaxValue(maxValue)
+                    .addEndListener((animation, canceled, value, velocity) -> {
+                        if (canceled) return;
+                        Runnable endAction = () -> {
+                            v.setStashed(shouldStash);
+                            v.updateLocation();
+                            if (!v.isStashed()) {
+                                mController.setLastPosition((int) v.getTranslationX(),
+                                        (int) v.getTranslationY());
+                            }
+                        };
+                        if (!shouldStash) {
+                            final int xTranslation = (int) v.getTranslationX();
+                            if (xTranslation != destinationRelativeX) {
+                                // TODO: this animation doesn't feel great, should figure out
+                                //  a better way to do this or remove the need for it all together.
+                                mViewPropertyAnimation = v.animate()
+                                        .translationX(destinationRelativeX)
+                                        .setListener(getAnimationListener(endAction));
+                                mViewPropertyAnimation.start();
+                            } else {
+                                endAction.run();
+                            }
+                        } else {
+                            endAction.run();
+                        }
+                    });
+            animations.add(flingAnimation);
+            return animations;
+        }
+
+        private AnimatorListenerAdapter getAnimationListener(Runnable endAction) {
+            return new AnimatorListenerAdapter() {
+                boolean translationCanceled = false;
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    super.onAnimationCancel(animation);
+                    translationCanceled = true;
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    if (!translationCanceled) {
+                        endAction.run();
+                    }
+                }
+            };
+        }
+
+        private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash,
+                float[] out) {
+            if (shouldStash) {
+                out[0] = onLeft
+                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
+                        : mPositionRect.right - v.getWidth();
+                out[1] = onLeft
+                        ? mPositionRect.left
+                        : mPositionRect.right - mOverhangWhenStashed;
+            } else {
+                out[0] = mPositionRect.left;
+                out[1] = mPositionRect.right - mTaskViewSize.x;
+            }
+        }
+
+        private float getMinimumVelocityToReachEdge(FloatingTaskView v,
+                float destinationRelativeX) {
+            // Minimum velocity required for the view to make it to the targeted side of the screen,
+            // taking friction into account (4.2f is the number that friction scalars are multiplied
+            // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the
+            // animation at the end will ensure that it reaches the destination X regardless.
+            return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f);
+        }
+
+        private float capX(FloatingTaskView v, float x, boolean isMoving) {
+            final int width = v.getWidth();
+            if (v.isStashed() || isMoving) {
+                if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) {
+                    return mPositionRect.left - v.getWidth() + mOverhangWhenStashed;
+                }
+                if (x > mPositionRect.right - mOverhangWhenStashed) {
+                    return mPositionRect.right - mOverhangWhenStashed;
+                }
+            } else {
+                if (x < mPositionRect.left) {
+                    return mPositionRect.left;
+                }
+                if (x > mPositionRect.right - width) {
+                    return mPositionRect.right - width;
+                }
+            }
+            return x;
+        }
+
+        private float capY(FloatingTaskView v, float y) {
+            final int height = v.getHeight();
+            if (y < mPositionRect.top) {
+                return mPositionRect.top;
+            }
+            if (y > mPositionRect.bottom - height) {
+                return mPositionRect.bottom - height;
+            }
+            return y;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
new file mode 100644
index 0000000..581204a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating.views;
+
+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;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTransitions;
+import com.android.wm.shell.bubbles.RelativeTouchListener;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.floating.FloatingTasksController;
+
+/**
+ * A view that holds a floating task using {@link TaskView} along with additional UI to manage
+ * the task.
+ */
+public class FloatingTaskView extends FrameLayout {
+
+    private static final String TAG = FloatingTaskView.class.getSimpleName();
+
+    private FloatingTasksController mController;
+
+    private FloatingMenuView mMenuView;
+    private int mMenuHeight;
+    private TaskView mTaskView;
+
+    private float mCornerRadius = 0f;
+    private int mBackgroundColor;
+
+    private FloatingTasksController.Task mTask;
+
+    private boolean mIsStashed;
+
+    /**
+     * Creates a floating task view.
+     *
+     * @param context the context to use.
+     * @param controller the controller to notify about changes in the floating task (e.g. removal).
+     */
+    public FloatingTaskView(Context context, FloatingTasksController controller) {
+        super(context);
+        mController = controller;
+        setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation));
+        mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size);
+        mMenuView = new FloatingMenuView(context);
+        addView(mMenuView);
+
+        applyThemeAttrs();
+
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+            }
+        });
+    }
+
+    // TODO: call this when theme/config changes
+    void applyThemeAttrs() {
+        boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+                mContext.getResources());
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+                android.R.attr.dialogCornerRadius,
+                android.R.attr.colorBackgroundFloating});
+        mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
+        mCornerRadius = mCornerRadius / 2f;
+        mBackgroundColor = ta.getColor(1, Color.WHITE);
+
+        ta.recycle();
+
+        mMenuView.setCornerRadius(mCornerRadius);
+        mMenuHeight = getResources().getDimensionPixelSize(
+                R.dimen.floating_task_menu_size);
+
+        if (mTaskView != null) {
+            mTaskView.setCornerRadius(mCornerRadius);
+        }
+    }
+
+    @Override
+    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,
+                MeasureSpec.getMode(heightMeasureSpec)));
+
+        if (mTaskView != null) {
+            int taskViewHeight = height - menuViewHeight;
+            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+                    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);
+        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());
+        }
+    }
+
+    /**
+     * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work.
+     */
+    public void createTaskView(Context context, ShellTaskOrganizer organizer,
+            TaskViewTransitions transitions, SyncTransactionQueue syncQueue) {
+        mTaskView = new TaskView(context, organizer, transitions, syncQueue);
+        addView(mTaskView);
+        mTaskView.setEnableSurfaceClipping(true);
+        mTaskView.setCornerRadius(mCornerRadius);
+    }
+
+    /**
+     * Starts the provided task in the TaskView, if the TaskView exists. This should be called after
+     * {@link #createTaskView}.
+     */
+    public void startTask(@ShellMainThread ShellExecutor executor,
+            FloatingTasksController.Task task) {
+        if (mTaskView == null) {
+            Log.e(TAG, "starting task before creating the view!");
+            return;
+        }
+        mTask = task;
+        mTaskView.setListener(executor, mTaskViewListener);
+    }
+
+    /**
+     * Sets the touch handler for the view.
+     *
+     * @param handler the touch handler for the view.
+     */
+    public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) {
+        setOnTouchListener(new RelativeTouchListener() {
+            @Override
+            public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+                handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY());
+                return true;
+            }
+
+            @Override
+            public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                    float viewInitialY, float dx, float dy) {
+                handler.onMove(FloatingTaskView.this, ev, dx, dy);
+            }
+
+            @Override
+            public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                    float viewInitialY, float dx, float dy, float velX, float velY) {
+                handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY);
+            }
+        });
+        setOnClickListener(view -> {
+            handler.onClick(FloatingTaskView.this);
+        });
+
+        mMenuView.addMenuItem(null, view -> {
+            if (mIsStashed) {
+                // If we're stashed all clicks un-stash.
+                handler.onClick(FloatingTaskView.this);
+            }
+        });
+    }
+
+    private void setContentVisibility(boolean visible) {
+        if (mTaskView == null) return;
+        mTaskView.setAlpha(visible ? 1f : 0f);
+    }
+
+    /**
+     * Sets the alpha of both this view and the TaskView.
+     */
+    public void setTaskViewAlpha(float alpha) {
+        if (mTaskView != null) {
+            mTaskView.setAlpha(alpha);
+        }
+        setAlpha(alpha);
+    }
+
+    /**
+     * Call when the location or size of the view has changed to update TaskView.
+     */
+    public void updateLocation() {
+        if (mTaskView == null) return;
+        mTaskView.onLocationChanged();
+    }
+
+    private void updateMenuColor() {
+        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
+        int color = info != null ? info.taskDescription.getBackgroundColor() : -1;
+        if (color != -1) {
+            mMenuView.setBackgroundColor(color);
+        } else {
+            mMenuView.setBackgroundColor(mBackgroundColor);
+        }
+    }
+
+    /**
+     * Sets whether the view is stashed or not.
+     *
+     * Also updates the touchable area based on this. If the view is stashed we don't direct taps
+     * on the activity to the activity, instead a tap will un-stash the view.
+     */
+    public void setStashed(boolean isStashed) {
+        if (mIsStashed != isStashed) {
+            mIsStashed = isStashed;
+            if (mTaskView == null) {
+                return;
+            }
+            updateObscuredTouchRect();
+        }
+    }
+
+    /** Whether the view is stashed at the edge of the screen or not. **/
+    public boolean isStashed() {
+        return mIsStashed;
+    }
+
+    private void updateObscuredTouchRect() {
+        if (mIsStashed) {
+            Rect tmpRect = new Rect();
+            getBoundsOnScreen(tmpRect);
+            mTaskView.setObscuredTouchRect(tmpRect);
+        } else {
+            mTaskView.setObscuredTouchRect(null);
+        }
+    }
+
+    /**
+     * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has
+     * been called on this view or if
+     * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called.
+     */
+    public boolean needsTaskStarted() {
+        // If the task needs to be restarted then TaskView would have been cleaned up.
+        return mTaskView == null;
+    }
+
+    /** Call this when the floating task activity is no longer in use. */
+    public void cleanUpTaskView() {
+        if (mTask != null && mTask.taskId != INVALID_TASK_ID) {
+            try {
+                ActivityTaskManager.getService().removeTask(mTask.taskId);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.getMessage());
+            }
+        }
+        if (mTaskView != null) {
+            mTaskView.release();
+            removeView(mTaskView);
+            mTaskView = null;
+        }
+    }
+
+    // TODO: use task background colour / how to get the taskInfo ?
+    private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) {
+        final int taskBgColor = taskInfo.taskDescription.getStatusBarColor();
+        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
+    }
+
+    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+        private boolean mInitialized = false;
+        private boolean mDestroyed = false;
+
+        @Override
+        public void onInitialized() {
+            if (mDestroyed || mInitialized) {
+                return;
+            }
+            // Custom options so there is no activity transition animation
+            ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                    /* enterResId= */ 0, /* exitResId= */ 0);
+
+            Rect launchBounds = new Rect();
+            mTaskView.getBoundsOnScreen(launchBounds);
+
+            try {
+                options.setTaskAlwaysOnTop(true);
+                if (mTask.intent != null) {
+                    Intent fillInIntent = new Intent();
+                    // Apply flags to make behaviour match documentLaunchMode=always.
+                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+                    PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent,
+                            PendingIntent.FLAG_MUTABLE,
+                            null);
+                    mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+                } else {
+                    ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent");
+                }
+            } catch (RuntimeException e) {
+                ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s",
+                        e.getMessage());
+                mController.removeTask();
+            }
+            mInitialized = true;
+        }
+
+        @Override
+        public void onReleased() {
+            mDestroyed = true;
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName name) {
+            mTask.taskId = taskId;
+            updateMenuColor();
+            setContentVisibility(true);
+        }
+
+        @Override
+        public void onTaskVisibilityChanged(int taskId, boolean visible) {
+            setContentVisibility(visible);
+        }
+
+        @Override
+        public void onTaskRemovalStarted(int taskId) {
+            // Must post because this is called from a binder thread.
+            post(() -> {
+                mController.removeTask();
+                cleanUpTaskView();
+            });
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(int taskId) {
+            if (mTask.taskId == taskId && !mIsStashed) {
+                // TODO: is removing the window the desired behavior?
+                post(() -> mController.removeTask());
+            }
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index f58719b..f82a346 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -19,16 +19,12 @@
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
 
 import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
@@ -41,31 +37,26 @@
 /**
  * {@link ShellTaskOrganizer.TaskListener} for {@link
  * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
- *
- * @param <T> the type of window decoration instance
  */
-public class FreeformTaskListener<T extends AutoCloseable>
-        implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
-    private final WindowDecorViewModel<T> mWindowDecorationViewModel;
+    private final WindowDecorViewModel mWindowDecorationViewModel;
 
-    private final SparseArray<State<T>> mTasks = new SparseArray<>();
-    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+    private final SparseArray<State> mTasks = new SparseArray<>();
 
-    private static class State<T extends AutoCloseable> {
+    private static class State {
         RunningTaskInfo mTaskInfo;
         SurfaceControl mLeash;
-        T mWindowDecoration;
     }
 
     public FreeformTaskListener(
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
-            WindowDecorViewModel<T> windowDecorationViewModel) {
+            WindowDecorViewModel windowDecorationViewModel) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -80,77 +71,54 @@
 
     @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        if (mTasks.get(taskInfo.taskId) != null) {
+            throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
                 taskInfo.taskId);
-        final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+        final State state = new State();
+        state.mTaskInfo = taskInfo;
+        state.mLeash = leash;
+        mTasks.put(taskInfo.taskId, state);
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            state.mWindowDecoration =
-                    mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+            mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
             t.apply();
         }
 
-        if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) {
+        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Adding active freeform task: #%d", taskInfo.taskId);
             mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
         }
     }
 
-    private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
-        State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            updateTaskInfo(taskInfo);
-            return state;
-        }
-
-        state = new State<>();
-        state.mTaskInfo = taskInfo;
-        state.mLeash = leash;
-        mTasks.put(taskInfo.taskId, state);
-
-        return state;
-    }
-
     @Override
     public void onTaskVanished(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            // This is possible if the transition happens before this method.
-            return;
-        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (DesktopMode.IS_SUPPORTED) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                     "Removing active freeform task: #%d", taskInfo.taskId);
             mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
         }
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // Save window decorations of closing tasks so that we can hand them over to the
-            // transition system if this method happens before the transition. In case where the
-            // transition didn't happen, it'd be cleared when the next transition finished.
-            if (state.mWindowDecoration != null) {
-                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
-            }
-            return;
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
         }
-        releaseWindowDecor(state.mWindowDecoration);
     }
 
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
-        final State<T> state = updateTaskInfo(taskInfo);
+        final State state = mTasks.get(taskInfo.taskId);
+        state.mTaskInfo = taskInfo;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
                 taskInfo.taskId);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
-        }
+        mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
 
-        if (DesktopMode.IS_SUPPORTED) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             if (taskInfo.isVisible) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                         "Adding active freeform task: #%d", taskInfo.taskId);
@@ -159,15 +127,6 @@
         }
     }
 
-    private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
-        }
-        state.mTaskInfo = taskInfo;
-        return state;
-    }
-
     @Override
     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
         b.setParent(findTaskSurface(taskId));
@@ -186,103 +145,6 @@
         return mTasks.get(taskId).mLeash;
     }
 
-    /**
-     * Creates a window decoration for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @return {@code true} if it creates the window decoration; {@code false} otherwise
-     */
-    boolean createWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        if (state.mWindowDecoration != null) {
-            return false;
-        }
-        state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
-                state.mTaskInfo, state.mLeash, startT, finishT);
-        return true;
-    }
-
-    /**
-     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
-     * left) this task listener. This is the transition system asking for the ownership.
-     *
-     * @param taskInfo the maximizing task
-     * @return the window decor of the maximizing task if any
-     */
-    T giveWindowDecoration(
-            RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        T windowDecor;
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            windowDecor = state.mWindowDecoration;
-            state.mWindowDecoration = null;
-        } else {
-            windowDecor =
-                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
-        }
-        if (windowDecor == null) {
-            return null;
-        }
-        mWindowDecorationViewModel.setupWindowDecorationForTransition(
-                taskInfo, startT, finishT, windowDecor);
-        return windowDecor;
-    }
-
-    /**
-     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @param startT the start transaction of this transition
-     * @param finishT the finish transaction of this transition
-     * @param windowDecor the window decoration to adopt
-     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
-     */
-    boolean adoptWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            @Nullable AutoCloseable windowDecor) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorationViewModel.setupWindowDecorationForTransition(
-                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
-            return true;
-        } else {
-            state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
-                    state.mTaskInfo, state.mLeash, startT, finishT);
-            return false;
-        }
-    }
-
-    void onTaskTransitionFinished() {
-        if (mWindowDecorOfVanishedTasks.size() == 0) {
-            return;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Clearing window decors of vanished tasks. There could be visual defects "
-                + "if any of them is used later in transitions.");
-        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
-            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
-        }
-        mWindowDecorOfVanishedTasks.clear();
-    }
-
-    private void releaseWindowDecor(T windowDecor) {
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
-        }
-    }
-
     @Override
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
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 fd4c85fa..04fc79a 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
@@ -46,14 +46,14 @@
         implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
 
     private final Transitions mTransitions;
-    private final WindowDecorViewModel<?> mWindowDecorViewModel;
+    private final WindowDecorViewModel mWindowDecorViewModel;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
     public FreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         mTransitions = transitions;
         mWindowDecorViewModel = windowDecorViewModel;
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 17d6067..f4888fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -16,13 +16,9 @@
 
 package com.android.wm.shell.freeform;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import android.app.ActivityManager;
 import android.content.Context;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -31,9 +27,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -47,23 +43,19 @@
  * be a part of transitions.
  */
 public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
-    private static final String TAG = "FreeformTO";
-
     private final Transitions mTransitions;
-    private final FreeformTaskListener<?> mFreeformTaskListener;
-    private final FullscreenTaskListener<?> mFullscreenTaskListener;
+    private final WindowDecorViewModel mWindowDecorViewModel;
 
-    private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();
+    private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
+            new HashMap<>();
 
     public FreeformTaskTransitionObserver(
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            FullscreenTaskListener<?> fullscreenTaskListener,
-            FreeformTaskListener<?> freeformTaskListener) {
+            WindowDecorViewModel windowDecorViewModel) {
         mTransitions = transitions;
-        mFreeformTaskListener = freeformTaskListener;
-        mFullscreenTaskListener = fullscreenTaskListener;
+        mWindowDecorViewModel = windowDecorViewModel;
         if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -80,7 +72,7 @@
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT) {
-        final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+        final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
         final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
@@ -110,92 +102,40 @@
                     onOpenTransitionReady(change, startT, finishT);
                     break;
                 case WindowManager.TRANSIT_CLOSE: {
-                    onCloseTransitionReady(change, windowDecors, startT, finishT);
+                    taskInfoList.add(change.getTaskInfo());
+                    onCloseTransitionReady(change, startT, finishT);
                     break;
                 }
                 case WindowManager.TRANSIT_CHANGE:
-                    onChangeTransitionReady(info.getType(), change, startT, finishT);
+                    onChangeTransitionReady(change, startT, finishT);
                     break;
             }
         }
-        if (!windowDecors.isEmpty()) {
-            mTransitionToWindowDecors.put(transition, windowDecors);
-        }
+        mTransitionToTaskInfo.put(transition, taskInfoList);
     }
 
     private void onOpenTransitionReady(
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        switch (change.getTaskInfo().getWindowingMode()){
-            case WINDOWING_MODE_FREEFORM:
-                mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
-                break;
-            case WINDOWING_MODE_FULLSCREEN:
-                mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
-                break;
-        }
+        mWindowDecorViewModel.createWindowDecoration(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
     private void onCloseTransitionReady(
             TransitionInfo.Change change,
-            ArrayList<AutoCloseable> windowDecors,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        final AutoCloseable windowDecor;
-        switch (change.getTaskInfo().getWindowingMode()) {
-            case WINDOWING_MODE_FREEFORM:
-                windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
-                        startT, finishT);
-                break;
-            case WINDOWING_MODE_FULLSCREEN:
-                windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
-                        startT, finishT);
-                break;
-            default:
-                windowDecor = null;
-        }
-        if (windowDecor != null) {
-            windowDecors.add(windowDecor);
-        }
+        mWindowDecorViewModel.setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     private void onChangeTransitionReady(
-            int type,
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        AutoCloseable windowDecor = null;
-
-        boolean adopted = false;
-        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-            windowDecor = mFreeformTaskListener.giveWindowDecoration(
-                    change.getTaskInfo(), startT, finishT);
-            if (windowDecor != null) {
-                adopted = mFullscreenTaskListener.adoptWindowDecoration(
-                        change, startT, finishT, windowDecor);
-            } else {
-                // will return false if it already has the window decor.
-                adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
-            }
-        }
-
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            windowDecor = mFullscreenTaskListener.giveWindowDecoration(
-                    change.getTaskInfo(), startT, finishT);
-            if (windowDecor != null) {
-                adopted = mFreeformTaskListener.adoptWindowDecoration(
-                        change, startT, finishT, windowDecor);
-            } else {
-                // will return false if it already has the window decor.
-                adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
-            }
-        }
-
-        if (!adopted) {
-            releaseWindowDecor(windowDecor);
-        }
+        mWindowDecorViewModel.setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     @Override
@@ -203,43 +143,32 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
-        if (windowDecorsOfMerged == null) {
+        final List<ActivityManager.RunningTaskInfo> infoOfMerged =
+                mTransitionToTaskInfo.get(merged);
+        if (infoOfMerged == null) {
             // We are adding window decorations of the merged transition to them of the playing
             // transition so if there is none of them there is nothing to do.
             return;
         }
-        mTransitionToWindowDecors.remove(merged);
+        mTransitionToTaskInfo.remove(merged);
 
-        final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
-        if (windowDecorsOfPlaying != null) {
-            windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
+        final List<ActivityManager.RunningTaskInfo> infoOfPlaying =
+                mTransitionToTaskInfo.get(playing);
+        if (infoOfPlaying != null) {
+            infoOfPlaying.addAll(infoOfMerged);
         } else {
-            mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
+            mTransitionToTaskInfo.put(playing, infoOfMerged);
         }
     }
 
     @Override
     public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
-        final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
-                transition, Collections.emptyList());
-        mTransitionToWindowDecors.remove(transition);
+        final List<ActivityManager.RunningTaskInfo> taskInfo =
+                mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
+        mTransitionToTaskInfo.remove(transition);
 
-        for (AutoCloseable windowDecor : windowDecors) {
-            releaseWindowDecor(windowDecor);
-        }
-        mFullscreenTaskListener.onTaskTransitionFinished();
-        mFreeformTaskListener.onTaskTransitionFinished();
-    }
-
-    private static void releaseWindowDecor(AutoCloseable windowDecor) {
-        if (windowDecor == null) {
-            return;
-        }
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
+        for (int i = 0; i < taskInfo.size(); ++i) {
+            mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 76e296b..75a4091 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -22,13 +22,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Point;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
-import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,23 +43,20 @@
   * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
  * @param <T> the type of window decoration instance
   */
-public class FullscreenTaskListener<T extends AutoCloseable>
-        implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FullscreenTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
 
-    private final SparseArray<State<T>> mTasks = new SparseArray<>();
-    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+    private final SparseArray<State> mTasks = new SparseArray<>();
 
-    private static class State<T extends AutoCloseable> {
+    private static class State {
         RunningTaskInfo mTaskInfo;
         SurfaceControl mLeash;
-        T mWindowDecoration;
     }
     private final SyncTransactionQueue mSyncQueue;
     private final Optional<RecentTasksController> mRecentTasksOptional;
-    private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
     /**
      * This constructor is used by downstream products.
      */
@@ -75,7 +69,7 @@
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
             Optional<RecentTasksController> recentTasksOptional,
-            Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
+            Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mRecentTasksOptional = recentTasksOptional;
@@ -98,21 +92,21 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                 taskInfo.taskId);
         final Point positionInParent = taskInfo.positionInParent;
-        final State<T> state = new State();
+        final State state = new State();
         state.mLeash = leash;
         state.mTaskInfo = taskInfo;
         mTasks.put(taskInfo.taskId, state);
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateRecentsForVisibleFullscreenTask(taskInfo);
+        boolean createdWindowDecor = false;
         if (mWindowDecorViewModelOptional.isPresent()) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            state.mWindowDecoration =
-                    mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
-                            leash, t, t);
+            createdWindowDecor = mWindowDecorViewModelOptional.get()
+                    .createWindowDecoration(taskInfo, leash, t, t);
             t.apply();
         }
-        if (state.mWindowDecoration == null) {
+        if (!createdWindowDecor) {
             mSyncQueue.runInSync(t -> {
                 // Reset several properties back to fullscreen (PiP, for example, leaves all these
                 // properties in a bad state).
@@ -127,12 +121,11 @@
 
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
+        final State state = mTasks.get(taskInfo.taskId);
         final Point oldPositionInParent = state.mTaskInfo.positionInParent;
         state.mTaskInfo = taskInfo;
-        if (state.mWindowDecoration != null) {
-            mWindowDecorViewModelOptional.get().onTaskInfoChanged(
-                    state.mTaskInfo, state.mWindowDecoration);
+        if (mWindowDecorViewModelOptional.isPresent()) {
+            mWindowDecorViewModelOptional.get().onTaskInfoChanged(state.mTaskInfo);
         }
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -147,160 +140,13 @@
 
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            // This is possible if the transition happens before this method.
-            return;
-        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // Save window decorations of closing tasks so that we can hand them over to the
-            // transition system if this method happens before the transition. In case where the
-            // transition didn't happen, it'd be cleared when the next transition finished.
-            if (state.mWindowDecoration != null) {
-                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
-            }
-            return;
-        }
-        releaseWindowDecor(state.mWindowDecoration);
-    }
-
-    /**
-     * Creates a window decoration for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @return {@code true} if a decoration was actually created.
-     */
-    public boolean createWindowDecoration(TransitionInfo.Change change,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        if (!mWindowDecorViewModelOptional.isPresent()) return false;
-        if (state.mWindowDecoration != null) {
-            // Already has a decoration.
-            return false;
-        }
-        T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
-                state.mTaskInfo, state.mLeash, startT, finishT);
-        if (newWindowDecor != null) {
-            state.mWindowDecoration = newWindowDecor;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @param startT the start transaction of this transition
-     * @param finishT the finish transaction of this transition
-     * @param windowDecor the window decoration to adopt
-     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
-     */
-    public boolean adoptWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            @Nullable AutoCloseable windowDecor) {
-        if (!mWindowDecorViewModelOptional.isPresent()) {
-            return false;
-        }
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
-                windowDecor);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
-                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
-            return true;
-        } else {
-            T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
-                    state.mTaskInfo, state.mLeash, startT, finishT);
-            if (newWindowDecor != null) {
-                state.mWindowDecoration = newWindowDecor;
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Clear window decors of vanished tasks.
-     */
-    public void onTaskTransitionFinished() {
-        if (mWindowDecorOfVanishedTasks.size() == 0) {
-            return;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Clearing window decors of vanished tasks. There could be visual defects "
-                + "if any of them is used later in transitions.");
-        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
-            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
-        }
-        mWindowDecorOfVanishedTasks.clear();
-    }
-
-    /**
-     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
-     * left) this task listener. This is the transition system asking for the ownership.
-     *
-     * @param taskInfo the maximizing task
-     * @return the window decor of the maximizing task if any
-     */
-    public T giveWindowDecoration(
-            ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        T windowDecor;
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            windowDecor = state.mWindowDecoration;
-            state.mWindowDecoration = null;
-        } else {
-            windowDecor =
-                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
-        }
-        if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
-            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
-                    taskInfo, startT, finishT, windowDecor);
-        }
-
-        return windowDecor;
-    }
-
-    private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl leash) {
-        State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            updateTaskInfo(taskInfo);
-            return state;
-        }
-
-        state = new State<T>();
-        state.mTaskInfo = taskInfo;
-        state.mLeash = leash;
-        mTasks.put(taskInfo.taskId, state);
-
-        return state;
-    }
-
-    private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        state.mTaskInfo = taskInfo;
-        return state;
-    }
-
-    private void releaseWindowDecor(T windowDecor) {
-        if (windowDecor == null) {
-            return;
-        }
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        if (mWindowDecorViewModelOptional.isPresent()) {
+            mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
         }
     }
 
@@ -342,6 +188,4 @@
     public String toString() {
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
-
-
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165..2ee3348 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@
             OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
 
     /**
-     * Returns a binder that can be passed to an external process to manipulate OneHanded.
-     */
-    default IOneHanded createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Enters one handed mode.
      */
     void startOneHanded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0c4fe8..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -24,6 +24,7 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
 
 import android.annotation.BinderThread;
 import android.content.ComponentName;
@@ -49,6 +50,7 @@
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+                this::createExternalInterface, this);
     }
 
     public OneHanded asOneHanded() {
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IOneHandedImpl(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
@@ -709,17 +717,6 @@
      */
     @ExternalThread
     private class OneHandedImpl implements OneHanded {
-        private IOneHandedImpl mIOneHanded;
-
-        @Override
-        public IOneHanded createExternalInterface() {
-            if (mIOneHanded != null) {
-                mIOneHanded.invalidate();
-            }
-            mIOneHanded = new IOneHandedImpl(OneHandedController.this);
-            return mIOneHanded;
-        }
-
         @Override
         public void startOneHanded() {
             mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IOneHandedImpl extends IOneHanded.Stub {
+    private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
         private OneHandedController mController;
 
         IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 4def15d..2624ee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -59,10 +59,15 @@
     /**
      * Sets listener to get pinned stack animation callbacks.
      */
-    oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+    oneway void setPipAnimationListener(IPipAnimationListener listener) = 3;
 
     /**
      * Sets the shelf height and visibility.
      */
     oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+
+    /**
+     * Sets the next pip animation type to be the alpha animation.
+     */
+    oneway void setPipAnimationTypeToAlpha() = 5;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index c06881a..f34d2a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@
  */
 @ExternalThread
 public interface Pip {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate PIP.
-     */
-    default IPip createExternalInterface() {
-        return null;
-    }
-
     /**
      * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
      */
@@ -51,15 +43,6 @@
     }
 
     /**
-     * Sets both shelf visibility and its height.
-     *
-     * @param visible visibility of shelf.
-     * @param height  to specify the height for shelf.
-     */
-    default void setShelfHeight(boolean visible, int height) {
-    }
-
-    /**
      * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
      *
      * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
@@ -68,14 +51,6 @@
     default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
 
     /**
-     * Set the pinned stack with {@link PipAnimationController.AnimationType}
-     *
-     * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
-     */
-    default void setPinnedStackAnimationType(int animationType) {
-    }
-
-    /**
      * Called when showing Pip menu.
      */
     default void showPictureInPictureMenu() {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b32c3ee..6728c00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -195,6 +195,17 @@
     }
 
     /**
+     * Returns true if the PiP window is currently being animated.
+     */
+    public boolean isAnimating() {
+        PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator();
+        if (animator != null && animator.isRunning()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Quietly cancel the animator by removing the listeners first.
      */
     static void quietCancel(@NonNull ValueAnimator animator) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1a52d8c..f170e77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -324,19 +324,6 @@
         return mPipTransitionController;
     }
 
-    /**
-     * Returns true if the PiP window is currently being animated.
-     */
-    public boolean isAnimating() {
-        // TODO(b/183746978) move this to PipAnimationController, and inject that in PipController
-        PipAnimationController.PipTransitionAnimator animator =
-                mPipAnimationController.getCurrentAnimator();
-        if (animator != null && animator.isRunning()) {
-            return true;
-        }
-        return false;
-    }
-
     public Rect getCurrentOrAnimatingBounds() {
         PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getCurrentAnimator();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index 29434f7..fa00619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -83,7 +83,9 @@
     public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) {
         if (action1 == action2) return true;
         if (action1 == null || action2 == null) return false;
-        return Objects.equals(action1.getTitle(), action2.getTitle())
+        return action1.isEnabled() == action2.isEnabled()
+                && action1.shouldShowIcon() == action2.shouldShowIcon()
+                && Objects.equals(action1.getTitle(), action2.getTitle())
                 && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
                 && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bc8191d..a918559 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -23,6 +23,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
@@ -32,6 +33,7 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -67,6 +69,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -130,6 +133,7 @@
     private DisplayController mDisplayController;
     private PipInputConsumer mPipInputConsumer;
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
+    private PipAnimationController mPipAnimationController;
     private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -158,7 +162,7 @@
             return;
         }
         // if there is another animation ongoing, wait for it to finish and try again
-        if (mPipTaskOrganizer.isAnimating()) {
+        if (mPipAnimationController.isAnimating()) {
             mMainExecutor.removeCallbacks(
                     mMovePipInResponseToKeepClearAreasChangeCallback);
             mMainExecutor.executeDelayed(
@@ -368,6 +372,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -392,11 +397,12 @@
         }
 
         return new PipController(context, shellInit, shellCommandHandler, shellController,
-                displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
-                pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
-                pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
-                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor)
+                displayController, pipAnimationController, pipAppOpsListener,
+                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
+                pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+                taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+                oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -405,6 +411,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -445,6 +452,7 @@
         mMediaController = pipMediaController;
         mMenuController = phonePipMenuController;
         mTouchHandler = pipTouchHandler;
+        mPipAnimationController = pipAnimationController;
         mAppOpsListener = pipAppOpsListener;
         mOneHandedController = oneHandedController;
         mPipTransitionController = pipTransitionController;
@@ -626,6 +634,12 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+                this::createExternalInterface, this);
+    }
+
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IPipImpl(this);
     }
 
     @Override
@@ -1034,17 +1048,6 @@
      * The interface for calls from outside the Shell, within the host process.
      */
     private class PipImpl implements Pip {
-        private IPipImpl mIPip;
-
-        @Override
-        public IPip createExternalInterface() {
-            if (mIPip != null) {
-                mIPip.invalidate();
-            }
-            mIPip = new IPipImpl(PipController.this);
-            return mIPip;
-        }
-
         @Override
         public void expandPip() {
             mMainExecutor.execute(() -> {
@@ -1060,13 +1063,6 @@
         }
 
         @Override
-        public void setShelfHeight(boolean visible, int height) {
-            mMainExecutor.execute(() -> {
-                PipController.this.setShelfHeight(visible, height);
-            });
-        }
-
-        @Override
         public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
             mMainExecutor.execute(() -> {
                 PipController.this.setOnIsInPipStateChangedListener(callback);
@@ -1074,13 +1070,6 @@
         }
 
         @Override
-        public void setPinnedStackAnimationType(int animationType) {
-            mMainExecutor.execute(() -> {
-                PipController.this.setPinnedStackAnimationType(animationType);
-            });
-        }
-
-        @Override
         public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
             mMainExecutor.execute(() -> {
                 mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
@@ -1106,7 +1095,7 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IPipImpl extends IPip.Stub {
+    private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
         private PipController mController;
         private final SingleInstanceRemoteListener<PipController,
                 IPipAnimationListener> mListener;
@@ -1137,7 +1126,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
@@ -1173,8 +1163,8 @@
         }
 
         @Override
-        public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+        public void setPipAnimationListener(IPipAnimationListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
                     (controller) -> {
                         if (listener != null) {
                             mListener.register(listener);
@@ -1183,5 +1173,13 @@
                         }
                     });
         }
+
+        @Override
+        public void setPipAnimationTypeToAlpha() {
+            executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
+                    (controller) -> {
+                        controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
+                    });
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index acc0caf..d7d335b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -43,16 +43,16 @@
      * <p>MAX - maximum allowed screen size</p>
      */
     @IntDef(value = {
-        SIZE_SPEC_CUSTOM,
         SIZE_SPEC_DEFAULT,
-        SIZE_SPEC_MAX
+        SIZE_SPEC_MAX,
+        SIZE_SPEC_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface PipSizeSpec {}
 
-    static final int SIZE_SPEC_CUSTOM = 2;
     static final int SIZE_SPEC_DEFAULT = 0;
     static final int SIZE_SPEC_MAX = 1;
+    static final int SIZE_SPEC_CUSTOM = 2;
 
     /**
      * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
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 ca22882..1651f89 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
@@ -87,6 +87,7 @@
     public void resetTvPipState() {
         mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
         mTvPipGravity = DEFAULT_TV_GRAVITY;
+        mTvPipManuallyCollapsed = false;
     }
 
     /** Set the tv expanded bounds of PiP */
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 85a544b..3e8de45 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
@@ -127,7 +127,7 @@
     private int mPipForceCloseDelay;
 
     private int mResizeAnimationDuration;
-    private int mEduTextWindowExitAnimationDurationMs;
+    private int mEduTextWindowExitAnimationDuration;
 
     public static Pip create(
             Context context,
@@ -240,12 +240,6 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
 
-        if (isPipShown()) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s:  > closing Pip.", TAG);
-            closePip();
-        }
-
         loadConfigurations();
         mPipNotificationController.onConfigurationChanged(mContext);
         mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
@@ -377,10 +371,10 @@
     }
 
     @Override
-    public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) {
-        mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds,
+    public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
+        mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
                 animationDuration, rect -> mTvPipMenuController.updateExpansionState());
-        mTvPipMenuController.onPipTransitionStarted(newTargetBounds);
+        mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
     }
 
     /**
@@ -417,7 +411,7 @@
 
     @Override
     public void closeEduText() {
-        updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false);
+        updatePinnedStackBounds(mEduTextWindowExitAnimationDuration, false);
     }
 
     private void registerSessionListenerForCurrentUser() {
@@ -459,27 +453,30 @@
     }
 
     @Override
-    public void onPipTransitionStarted(int direction, Rect pipBounds) {
+    public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
-        mTvPipMenuController.notifyPipAnimating(true);
+                "%s: onPipTransition_Started(), state=%s, direction=%d",
+                TAG, stateToName(mState), direction);
     }
 
     @Override
     public void onPipTransitionCanceled(int direction) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
-        mTvPipMenuController.notifyPipAnimating(false);
+        mTvPipMenuController.onPipTransitionFinished(
+                PipAnimationController.isInPipDirection(direction));
     }
 
     @Override
     public void onPipTransitionFinished(int direction) {
-        if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) {
+        final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+        if (enterPipTransition && mState == STATE_NO_PIP) {
             setState(STATE_PIP);
         }
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
-        mTvPipMenuController.notifyPipAnimating(false);
+                "%s: onPipTransition_Finished(), state=%s, direction=%d",
+                TAG, stateToName(mState), direction);
+        mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
     }
 
     private void setState(@State int state) {
@@ -493,8 +490,8 @@
         final Resources res = mContext.getResources();
         mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
         mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
-        mEduTextWindowExitAnimationDurationMs =
-                res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+        mEduTextWindowExitAnimationDuration =
+                res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
     }
 
     private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 176fdfe..ab7edbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -60,9 +60,6 @@
     private final SystemWindows mSystemWindows;
     private final TvPipBoundsState mTvPipBoundsState;
     private final Handler mMainHandler;
-    private final int mPipMenuBorderWidth;
-    private final int mPipEduTextShowDurationMs;
-    private final int mPipEduTextHeight;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
@@ -85,8 +82,6 @@
     RectF mTmpDestinationRectF = new RectF();
     Matrix mMoveTransform = new Matrix();
 
-    private final Runnable mCloseEduTextRunnable = this::closeEduText;
-
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows, PipMediaController pipMediaController,
             Handler mainHandler) {
@@ -109,12 +104,6 @@
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
 
-        mPipEduTextShowDurationMs = context.getResources()
-                .getInteger(R.integer.pip_edu_text_show_duration_ms);
-        mPipEduTextHeight = context.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
-        mPipMenuBorderWidth = context.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
     }
 
     void setDelegate(Delegate delegate) {
@@ -152,15 +141,17 @@
         attachPipBackgroundView();
         attachPipMenuView();
 
-        mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
-                -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
-        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
-        mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+        int pipEduTextHeight = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+        int pipMenuBorderWidth = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+        mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
+                    -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
     }
 
     private void attachPipMenuView() {
-        mPipMenuView = new TvPipMenuView(mContext);
-        mPipMenuView.setListener(this);
+        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this);
         setUpViewSurfaceZOrder(mPipMenuView, 1);
         addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
         maybeUpdateMenuViewActions();
@@ -192,11 +183,15 @@
                 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
     }
 
-    void notifyPipAnimating(boolean animating) {
-        mPipMenuView.setEduTextActive(!animating);
-        if (!animating) {
-            mPipMenuView.onPipTransitionFinished(mTvPipBoundsState.isTvPipExpanded());
-        }
+    void onPipTransitionFinished(boolean enterTransition) {
+        // There is a race between when this is called and when the last frame of the pip transition
+        // is drawn. To ensure that view updates are applied only when the animation has fully drawn
+        // and the menu view has been fully remeasured and relaid out, we add a small delay here by
+        // posting on the handler.
+        mMainHandler.post(() -> {
+            mPipMenuView.onPipTransitionFinished(
+                    enterTransition, mTvPipBoundsState.isTvPipExpanded());
+        });
     }
 
     void showMovementMenuOnly() {
@@ -219,7 +214,6 @@
         if (mPipMenuView == null) {
             return;
         }
-        maybeCloseEduText();
         maybeUpdateMenuViewActions();
         updateExpansionState();
 
@@ -232,25 +226,12 @@
         mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
     }
 
-    void onPipTransitionStarted(Rect finishBounds) {
+    void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
         if (mPipMenuView != null) {
-            mPipMenuView.onPipTransitionStarted(finishBounds);
+            mPipMenuView.onPipTransitionToTargetBoundsStarted(targetBounds);
         }
     }
 
-    private void maybeCloseEduText() {
-        if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
-            mMainHandler.removeCallbacks(mCloseEduTextRunnable);
-            mCloseEduTextRunnable.run();
-        }
-    }
-
-    private void closeEduText() {
-        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
-        mPipMenuView.hideEduText();
-        mDelegate.closeEduText();
-    }
-
     void updateGravity(int gravity) {
         mPipMenuView.showMovementHints(gravity);
     }
@@ -332,7 +313,6 @@
     @Override
     public void detach() {
         closeMenu();
-        mMainHandler.removeCallbacks(mCloseEduTextRunnable);
         detachPipMenu();
         mLeash = null;
     }
@@ -578,6 +558,12 @@
         mDelegate.togglePipExpansion();
     }
 
+    @Override
+    public void onCloseEduText() {
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+        mDelegate.closeEduText();
+    }
+
     interface Delegate {
         void movePipToFullscreen();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
new file mode 100644
index 0000000..6eef225
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pip.tv;
+
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.CENTER;
+import static android.view.View.GONE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+
+import java.util.Arrays;
+
+/**
+ * The edu text drawer shows the user a hint for how to access the Picture-in-Picture menu.
+ * It displays a text in a drawer below the Picture-in-Picture window. The drawer has the same
+ * width as the Picture-in-Picture window. Depending on the Picture-in-Picture mode, there might
+ * not be enough space to fit the whole educational text in the available space. In such cases we
+ * apply a marquee animation to the TextView inside the drawer.
+ *
+ * The drawer is shown temporarily giving the user enough time to read it, after which it slides
+ * shut. We show the text for a duration calculated based on whether the text is marqueed or not.
+ */
+class TvPipMenuEduTextDrawer extends FrameLayout {
+    private static final String TAG = "TvPipMenuEduTextDrawer";
+
+    private static final float MARQUEE_DP_PER_SECOND = 30; // Copy of TextView.MARQUEE_DP_PER_SECOND
+    private static final int MARQUEE_RESTART_DELAY = 1200; // Copy of TextView.MARQUEE_DELAY
+    private final float mMarqueeAnimSpeed; // pixels per ms
+
+    private final Runnable mCloseDrawerRunnable = this::closeDrawer;
+    private final Runnable mStartScrollEduTextRunnable = this::startScrollEduText;
+
+    private final Handler mMainHandler;
+    private final Listener mListener;
+    private final TextView mEduTextView;
+
+    TvPipMenuEduTextDrawer(@NonNull Context context, Handler mainHandler, Listener listener) {
+        super(context, null, 0, 0);
+
+        mListener = listener;
+        mMainHandler = mainHandler;
+
+        // Taken from TextView.Marquee calculation
+        mMarqueeAnimSpeed =
+            (MARQUEE_DP_PER_SECOND * context.getResources().getDisplayMetrics().density) / 1000f;
+
+        mEduTextView = new TextView(mContext);
+        setupDrawer();
+    }
+
+    private void setupDrawer() {
+        final int eduTextHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.pip_menu_edu_text_view_height);
+        final int marqueeRepeatLimit = mContext.getResources()
+                .getInteger(R.integer.pip_edu_text_scroll_times);
+
+        mEduTextView.setLayoutParams(
+                new LayoutParams(MATCH_PARENT, eduTextHeight, BOTTOM | CENTER));
+        mEduTextView.setGravity(CENTER);
+        mEduTextView.setClickable(false);
+        mEduTextView.setText(createEduTextString());
+        mEduTextView.setSingleLine();
+        mEduTextView.setTextAppearance(R.style.TvPipEduText);
+        mEduTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+        mEduTextView.setMarqueeRepeatLimit(marqueeRepeatLimit);
+        mEduTextView.setHorizontallyScrolling(true);
+        mEduTextView.setHorizontalFadingEdgeEnabled(true);
+        mEduTextView.setSelected(false);
+        addView(mEduTextView);
+
+        setLayoutParams(new LayoutParams(MATCH_PARENT, eduTextHeight, CENTER));
+        setClipChildren(true);
+    }
+
+    /**
+     * Initializes the edu text. Should only be called once when the PiP is entered
+     */
+    void init() {
+        ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: init()", TAG);
+        scheduleLifecycleEvents();
+    }
+
+    private void scheduleLifecycleEvents() {
+        final int startScrollDelay = mContext.getResources().getInteger(
+                R.integer.pip_edu_text_start_scroll_delay);
+        if (isEduTextMarqueed()) {
+            mMainHandler.postDelayed(mStartScrollEduTextRunnable, startScrollDelay);
+        }
+        mMainHandler.postDelayed(mCloseDrawerRunnable, startScrollDelay + getEduTextShowDuration());
+        mEduTextView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                @Override
+                public void onWindowAttached() {
+                }
+
+                @Override
+                public void onWindowDetached() {
+                    mEduTextView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                    mMainHandler.removeCallbacks(mStartScrollEduTextRunnable);
+                    mMainHandler.removeCallbacks(mCloseDrawerRunnable);
+                }
+            });
+    }
+
+    private int getEduTextShowDuration() {
+        int eduTextShowDuration;
+        if (isEduTextMarqueed()) {
+            // Calculate the time it takes to fully scroll the text once: time = distance / speed
+            final float singleMarqueeDuration =
+                    getMarqueeAnimEduTextLineWidth() / mMarqueeAnimSpeed;
+            // The TextView adds a delay between each marquee repetition. Take that into account
+            final float durationFromStartToStart = singleMarqueeDuration + MARQUEE_RESTART_DELAY;
+            // Finally, multiply by the number of times we repeat the marquee animation
+            eduTextShowDuration =
+                    (int) durationFromStartToStart * mEduTextView.getMarqueeRepeatLimit();
+        } else {
+            eduTextShowDuration = mContext.getResources()
+                    .getInteger(R.integer.pip_edu_text_non_scroll_show_duration);
+        }
+
+        ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: getEduTextShowDuration(), showDuration=%d",
+                TAG, eduTextShowDuration);
+        return eduTextShowDuration;
+    }
+
+    /**
+     * Returns true if the edu text width is bigger than the width of the text view, which indicates
+     * that the edu text will be marqueed
+     */
+    private boolean isEduTextMarqueed() {
+        final int availableWidth = (int) mEduTextView.getWidth()
+                - mEduTextView.getCompoundPaddingLeft()
+                - mEduTextView.getCompoundPaddingRight();
+        return availableWidth < getEduTextWidth();
+    }
+
+    /**
+     * Returns the width of a single marquee repetition of the edu text in pixels.
+     * This is the width from the start of the edu text to the start of the next edu
+     * text when it is marqueed.
+     *
+     * This is calculated based on the TextView.Marquee#start calculations
+     */
+    private float getMarqueeAnimEduTextLineWidth() {
+        // When the TextView has a marquee animation, it puts a gap between the text end and the
+        // start of the next edu text repetition. The space is equal to a third of the TextView
+        // width
+        final float gap = mEduTextView.getWidth() / 3.0f;
+        return getEduTextWidth() + gap;
+    }
+
+    private void startScrollEduText() {
+        ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: startScrollEduText(), repeat=%d",
+                TAG, mEduTextView.getMarqueeRepeatLimit());
+        mEduTextView.setSelected(true);
+    }
+
+    /**
+     * Returns the width of the edu text irrespective of the TextView width
+     */
+    private int getEduTextWidth() {
+        return (int) mEduTextView.getLayout().getLineWidth(0);
+    }
+
+    /**
+     * Closes the edu text drawer if it hasn't been closed yet
+     */
+    void closeIfNeeded() {
+        if (mMainHandler.hasCallbacks(mCloseDrawerRunnable)) {
+            ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: close(), closing the edu text drawer because of user action", TAG);
+            mMainHandler.removeCallbacks(mCloseDrawerRunnable);
+            mCloseDrawerRunnable.run();
+        } else {
+            // Do nothing, the drawer has already been closed
+        }
+    }
+
+    private void closeDrawer() {
+        ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: closeDrawer()", TAG);
+        final int eduTextFadeExitAnimationDuration = mContext.getResources().getInteger(
+                R.integer.pip_edu_text_view_exit_animation_duration);
+        final int eduTextSlideExitAnimationDuration = mContext.getResources().getInteger(
+                R.integer.pip_edu_text_window_exit_animation_duration);
+
+        // Start fading out the edu text
+        mEduTextView.animate()
+                .alpha(0f)
+                .setInterpolator(TvPipInterpolators.EXIT)
+                .setDuration(eduTextFadeExitAnimationDuration)
+                .start();
+
+        // Start animation to close the drawer by animating its height to 0
+        final ValueAnimator heightAnimation = ValueAnimator.ofInt(getHeight(), 0);
+        heightAnimation.setDuration(eduTextSlideExitAnimationDuration);
+        heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+        heightAnimation.addUpdateListener(animator -> {
+            final ViewGroup.LayoutParams params = getLayoutParams();
+            params.height = (int) animator.getAnimatedValue();
+            setLayoutParams(params);
+            if (params.height == 0) {
+                setVisibility(GONE);
+            }
+        });
+        heightAnimation.start();
+
+        mListener.onCloseEduText();
+    }
+
+    /**
+     * Creates the educational text that will be displayed to the user. Here we replace the
+     * HOME annotation in the String with an icon
+     */
+    private CharSequence createEduTextString() {
+        final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+        final SpannableString spannableString = new SpannableString(eduText);
+        Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+                .ifPresent(annotation -> {
+                    final Drawable icon =
+                            getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+                    if (icon != null) {
+                        icon.mutate();
+                        icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+                        spannableString.setSpan(new CenteredImageSpan(icon),
+                                eduText.getSpanStart(annotation),
+                                eduText.getSpanEnd(annotation),
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                });
+
+        return spannableString;
+    }
+
+    /**
+     * A listener for edu text drawer event states.
+     */
+    interface Listener {
+        /**
+         *  The edu text closing impacts the size of the Picture-in-Picture window and influences
+         *  how it is positioned on the screen.
+         */
+        void onCloseEduText();
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 9cd05b0..57e95c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,18 +25,11 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
-import android.animation.ValueAnimator;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.text.Annotation;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannedString;
-import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.SurfaceControl;
@@ -49,7 +42,6 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -61,7 +53,6 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -74,21 +65,16 @@
 
     private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
 
-    @Nullable
-    private Listener mListener;
+    private final Listener mListener;
 
     private final LinearLayout mActionButtonsContainer;
     private final View mMenuFrameView;
     private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
     private final View mPipFrameView;
     private final View mPipView;
-    private final TextView mEduTextView;
-    private final View mEduTextContainerView;
+    private final TvPipMenuEduTextDrawer mEduTextDrawer;
     private final int mPipMenuOuterSpace;
     private final int mPipMenuBorderWidth;
-    private final int mEduTextFadeExitAnimationDurationMs;
-    private final int mEduTextSlideExitAnimationDurationMs;
-    private int mEduTextHeight;
 
     private final ImageView mArrowUp;
     private final ImageView mArrowRight;
@@ -116,25 +102,17 @@
     private final int mResizeAnimationDuration;
 
     private final AccessibilityManager mA11yManager;
+    private final Handler mMainHandler;
 
-    public TvPipMenuView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
+    public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
+            @NonNull Listener listener) {
+        super(context, null, 0, 0);
 
         inflate(context, R.layout.tv_pip_menu, this);
 
+        mMainHandler = mainHandler;
+        mListener = listener;
+
         mA11yManager = context.getSystemService(AccessibilityManager.class);
 
         mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
@@ -166,9 +144,6 @@
         mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
         mA11yDoneButton = findViewById(R.id.tv_pip_menu_done_button);
 
-        mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
-        mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
-
         mResizeAnimationDuration = context.getResources().getInteger(
                 R.integer.config_pipResizeAnimationDuration);
         mPipMenuFadeAnimationDuration = context.getResources()
@@ -178,63 +153,18 @@
                 .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
         mPipMenuBorderWidth = context.getResources()
                 .getDimensionPixelSize(R.dimen.pip_menu_border_width);
-        mEduTextHeight = context.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
-        mEduTextFadeExitAnimationDurationMs = context.getResources()
-                .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
-        mEduTextSlideExitAnimationDurationMs = context.getResources()
-                .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
 
-        initEduText();
+        mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, mListener);
+        ((FrameLayout) findViewById(R.id.tv_pip_menu_edu_text_drawer_placeholder))
+                .addView(mEduTextDrawer);
     }
 
-    void initEduText() {
-        final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
-        final SpannableString spannableString = new SpannableString(eduText);
-        Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
-                .ifPresent(annotation -> {
-                    final Drawable icon =
-                            getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
-                    if (icon != null) {
-                        icon.mutate();
-                        icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
-                        spannableString.setSpan(new CenteredImageSpan(icon),
-                                eduText.getSpanStart(annotation),
-                                eduText.getSpanEnd(annotation),
-                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                });
-
-        mEduTextView.setText(spannableString);
-    }
-
-    void setEduTextActive(boolean active) {
-        mEduTextView.setSelected(active);
-    }
-
-    void hideEduText() {
-        final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
-        heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
-        heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
-        heightAnimation.addUpdateListener(animator -> {
-            mEduTextHeight = (int) animator.getAnimatedValue();
-        });
-        mEduTextView.animate()
-                .alpha(0f)
-                .setInterpolator(TvPipInterpolators.EXIT)
-                .setDuration(mEduTextFadeExitAnimationDurationMs)
-                .withEndAction(() -> {
-                    mEduTextContainerView.setVisibility(GONE);
-                }).start();
-        heightAnimation.start();
-    }
-
-    void onPipTransitionStarted(Rect finishBounds) {
+    void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
         // Fade out content by fading in view on top.
-        if (mCurrentPipBounds != null && finishBounds != null) {
+        if (mCurrentPipBounds != null && targetBounds != null) {
             boolean ratioChanged = PipUtils.aspectRatioChanged(
                     mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
-                    finishBounds.width() / (float) finishBounds.height());
+                    targetBounds.width() / (float) targetBounds.height());
             if (ratioChanged) {
                 mPipBackground.animate()
                         .alpha(1f)
@@ -245,11 +175,12 @@
         }
 
         // Update buttons.
-        final boolean vertical = finishBounds.height() > finishBounds.width();
+        final boolean vertical = targetBounds.height() > targetBounds.width();
         final boolean orientationChanged =
                 vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onPipTransitionStarted(), orientation changed %b", TAG, orientationChanged);
+                "%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
+                TAG, orientationChanged);
         if (!orientationChanged) {
             return;
         }
@@ -261,18 +192,18 @@
                     .setInterpolator(TvPipInterpolators.EXIT)
                     .setDuration(mResizeAnimationDuration / 2)
                     .withEndAction(() -> {
-                        changeButtonScrollOrientation(finishBounds);
-                        updateButtonGravity(finishBounds);
+                        changeButtonScrollOrientation(targetBounds);
+                        updateButtonGravity(targetBounds);
                         // Only make buttons visible again in onPipTransitionFinished to keep in
                         // sync with PiP content alpha animation.
                     });
         } else {
-            changeButtonScrollOrientation(finishBounds);
-            updateButtonGravity(finishBounds);
+            changeButtonScrollOrientation(targetBounds);
+            updateButtonGravity(targetBounds);
         }
     }
 
-    void onPipTransitionFinished(boolean isTvPipExpanded) {
+    void onPipTransitionFinished(boolean enterTransition, boolean isTvPipExpanded) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransitionFinished()", TAG);
 
@@ -283,6 +214,10 @@
                 .setInterpolator(TvPipInterpolators.ENTER)
                 .start();
 
+        if (enterTransition) {
+            mEduTextDrawer.init();
+        }
+
         setIsExpanded(isTvPipExpanded);
 
         // Update buttons.
@@ -409,7 +344,7 @@
     Rect getPipMenuContainerBounds(Rect pipBounds) {
         final Rect menuUiBounds = new Rect(pipBounds);
         menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
-        menuUiBounds.bottom += mEduTextHeight;
+        menuUiBounds.bottom += mEduTextDrawer.getHeight();
         return menuUiBounds;
     }
 
@@ -438,10 +373,6 @@
 
     }
 
-    void setListener(@Nullable Listener listener) {
-        mListener = listener;
-    }
-
     void setExpandedModeEnabled(boolean enabled) {
         mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
     }
@@ -460,21 +391,19 @@
      */
     void showMoveMenu(int gravity) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
-        mButtonMenuIsVisible = false;
-        mMoveMenuIsVisible = true;
         showButtonsMenu(false);
         showMovementHints(gravity);
         setFrameHighlighted(true);
 
         mHorizontalScrollView.setFocusable(false);
         mScrollView.setFocusable(false);
+
+        mEduTextDrawer.closeIfNeeded();
     }
 
     void showButtonsMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showButtonsMenu()", TAG);
-        mButtonMenuIsVisible = true;
-        mMoveMenuIsVisible = false;
         showButtonsMenu(true);
         hideMovementHints();
         setFrameHighlighted(true);
@@ -501,8 +430,6 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideAllUserControls()", TAG);
         mFocusedButton = null;
-        mButtonMenuIsVisible = false;
-        mMoveMenuIsVisible = false;
         showButtonsMenu(false);
         hideMovementHints();
         setFrameHighlighted(false);
@@ -632,8 +559,6 @@
 
     @Override
     public void onClick(View v) {
-        if (mListener == null) return;
-
         final int id = v.getId();
         if (id == R.id.tv_pip_menu_fullscreen_button) {
             mListener.onFullscreenButtonClick();
@@ -662,7 +587,7 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (mListener != null && event.getAction() == ACTION_UP) {
+        if (event.getAction() == ACTION_UP) {
             if (!mMoveMenuIsVisible) {
                 mFocusedButton = mActionButtonsContainer.getFocusedChild();
             }
@@ -700,6 +625,11 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
 
+        if (mMoveMenuIsVisible) {
+            return;
+        }
+        mMoveMenuIsVisible = true;
+
         animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
         animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
         animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
@@ -714,9 +644,7 @@
         animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
         if (a11yEnabled) {
             mA11yDoneButton.setOnClickListener(v -> {
-                if (mListener != null) {
-                    mListener.onExitMoveMode();
-                }
+                mListener.onExitMoveMode();
             });
         }
     }
@@ -725,9 +653,7 @@
         arrowView.setClickable(enabled);
         if (enabled) {
             arrowView.setOnClickListener(v -> {
-                if (mListener != null) {
-                    mListener.onPipMovement(keycode);
-                }
+                mListener.onPipMovement(keycode);
             });
         }
     }
@@ -743,6 +669,11 @@
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideMovementHints()", TAG);
 
+        if (!mMoveMenuIsVisible) {
+            return;
+        }
+        mMoveMenuIsVisible = false;
+
         animateAlphaTo(0, mArrowUp);
         animateAlphaTo(0, mArrowRight);
         animateAlphaTo(0, mArrowDown);
@@ -756,19 +687,25 @@
     public void showButtonsMenu(boolean show) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showUserActions: %b", TAG, show);
+        if (mButtonMenuIsVisible == show) {
+            return;
+        }
+        mButtonMenuIsVisible = show;
+
         if (show) {
             mActionButtonsContainer.setVisibility(VISIBLE);
             refocusPreviousButton();
         }
         animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
         animateAlphaTo(show ? 1 : 0, mDimLayer);
+        mEduTextDrawer.closeIfNeeded();
     }
 
     private void setFrameHighlighted(boolean highlighted) {
         mMenuFrameView.setActivated(highlighted);
     }
 
-    interface Listener {
+    interface Listener extends TvPipMenuEduTextDrawer.Listener {
 
         void onBackPress();
 
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 3fef823..c52ed24 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
@@ -48,6 +48,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_SHELL),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
 package com.android.wm.shell.protolog;
 
 import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.BaseProtoLogImpl;
 import com.android.internal.protolog.ProtoLogViewerConfigReader;
 import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
 
-import org.json.JSONException;
-
 
 /**
  * A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@
 public class ShellProtoLogImpl extends BaseProtoLogImpl {
     private static final String TAG = "ProtoLogImpl";
     private static final int BUFFER_CAPACITY = 1024 * 1024;
-    // TODO: Get the right path for the proto log file when we initialize the shell components
-    private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+    // TODO: find a proper location to save the protolog message file
+    private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+    private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
 
     private static ShellProtoLogImpl sServiceInstance = null;
 
@@ -111,18 +104,8 @@
     }
 
     public int startTextLogging(String[] groups, PrintWriter pw) {
-        try (InputStream is =
-                     getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
-            mViewerConfig.loadViewerConfig(is);
-            return setLogging(true /* setTextLogging */, true, pw, groups);
-        } catch (IOException e) {
-            Log.i(TAG, "Unable to load log definitions: IOException while reading "
-                    + "wm_shell_protolog. " + e);
-        } catch (JSONException e) {
-            Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
-                    + "wm_shell_protolog. " + e);
-        }
-        return -1;
+        mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+        return setLogging(true /* setTextLogging */, true, pw, groups);
     }
 
     public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@
     }
 
     private ShellProtoLogImpl() {
-        super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+        super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+                new ProtoLogViewerConfigReader());
     }
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index a5748f6..069066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -17,6 +17,11 @@
 package com.android.wm.shell.recents;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Interface for interacting with the recent tasks.
@@ -24,9 +29,9 @@
 @ExternalThread
 public interface RecentTasks {
     /**
-     * Returns a binder that can be passed to an external process to fetch recent tasks.
+     * Gets the set of recent tasks.
      */
-    default IRecentTasks createExternalInterface() {
-        return null;
+    default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
+            Consumer<List<GroupedRecentTaskInfo>> callback) {
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index ff4b2ed..08f3db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,10 +20,12 @@
 import static android.content.pm.PackageManager.FEATURE_PC;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.TaskInfo;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -36,6 +38,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -43,10 +46,11 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 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.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
@@ -57,6 +61,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
@@ -66,11 +72,13 @@
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
-    private final RecentTasks mImpl = new RecentTasksImpl();
+    private final RecentTasksImpl mImpl = new RecentTasksImpl();
+    private final ActivityTaskManager mActivityTaskManager;
     private IRecentTasksListener mListener;
     private final boolean mIsDesktopMode;
 
@@ -93,26 +101,32 @@
     public static RecentTasksController create(
             Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
             return null;
         }
-        return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                desktopModeTaskRepository, mainExecutor);
+        return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+                taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mShellController = shellController;
         mShellCommandHandler = shellCommandHandler;
+        mActivityTaskManager = activityTaskManager;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -124,7 +138,13 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IRecentTasksImpl(this);
+    }
+
     private void onInit() {
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+                this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mTaskStackListener.addListener(this);
         mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -269,15 +289,10 @@
     }
 
     @VisibleForTesting
-    List<ActivityManager.RecentTaskInfo> getRawRecentTasks(int maxNum, int flags, int userId) {
-        return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId);
-    }
-
-    @VisibleForTesting
     ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is from the most-recent to least-recent order
-        final List<ActivityManager.RecentTaskInfo> rawList = getRawRecentTasks(maxNum, flags,
-                userId);
+        final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+                maxNum, flags, userId);
 
         // Make a mapping of task id -> task info
         final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
@@ -286,7 +301,7 @@
             rawMapping.put(taskInfo.taskId, taskInfo);
         }
 
-        boolean desktopModeActive = DesktopMode.isActive(mContext);
+        boolean desktopModeActive = DesktopModeStatus.isActive(mContext);
         ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
 
         // Pull out the pairs as we iterate back in the list
@@ -319,7 +334,6 @@
 
         // Add a special entry for freeform tasks
         if (!freeformTasks.isEmpty()) {
-            // First task is added separately
             recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks(
                     freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
         }
@@ -327,6 +341,29 @@
         return recentTasks;
     }
 
+    /**
+     * Find the background task that match the given component.
+     */
+    @Nullable
+    public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) {
+        if (componentName == null) {
+            return null;
+        }
+        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+                Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+                ActivityManager.getCurrentUser());
+        for (int i = 0; i < tasks.size(); i++) {
+            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            if (task.isVisible) {
+                continue;
+            }
+            if (componentName.equals(task.baseIntent.getComponent())) {
+                return task;
+            }
+        }
+        return null;
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
@@ -342,15 +379,14 @@
      */
     @ExternalThread
     private class RecentTasksImpl implements RecentTasks {
-        private IRecentTasksImpl mIRecentTasks;
-
         @Override
-        public IRecentTasks createExternalInterface() {
-            if (mIRecentTasks != null) {
-                mIRecentTasks.invalidate();
-            }
-            mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
-            return mIRecentTasks;
+        public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
+                Consumer<List<GroupedRecentTaskInfo>> callback) {
+            mMainExecutor.execute(() -> {
+                List<GroupedRecentTaskInfo> tasks =
+                        RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
+                executor.execute(() -> callback.accept(tasks));
+            });
         }
     }
 
@@ -359,7 +395,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IRecentTasksImpl extends IRecentTasks.Stub {
+    private static class IRecentTasksImpl extends IRecentTasks.Stub
+            implements ExternalInterfaceBinder {
         private RecentTasksController mController;
         private final SingleInstanceRemoteListener<RecentTasksController,
                 IRecentTasksListener> mListener;
@@ -390,7 +427,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799..d86aadc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,13 +70,6 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
-    /**
-     * Returns a binder that can be passed to an external process to manipulate SplitScreen.
-     */
-    default ISplitScreen createExternalInterface() {
-        return null;
-    }
-
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
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 991f136..eeb2c0f 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
@@ -29,6 +29,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 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;
 
 import android.app.ActivityManager;
@@ -71,6 +72,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.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new ISplitScreenImpl(this);
+    }
+
     /**
      * This will be called after ShellTaskOrganizer has initialized/registered because of the
      * dependency order.
@@ -224,6 +230,8 @@
         mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
                 this);
         mShellController.addKeyguardChangeListener(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+                this::createExternalInterface, this);
         if (mStageCoordinator == null) {
             // TODO: Multi-display
             mStageCoordinator = createStageCoordinator();
@@ -385,6 +393,9 @@
             }
             @Override
             public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+                mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct);
+                mSyncQueue.queue(evictWct);
             }
         };
         options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
@@ -472,8 +483,16 @@
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
         // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
-        // split.
+        // split and there is no reusable background task.
         if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
+            final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
+                    ? mRecentTasksOptional.get().findTaskInBackground(
+                            intent.getIntent().getComponent())
+                    : null;
+            if (taskInfo != null) {
+                startTask(taskInfo.taskId, position, options);
+                return;
+            }
             fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
         }
@@ -647,7 +666,6 @@
      */
     @ExternalThread
     private class SplitScreenImpl implements SplitScreen {
-        private ISplitScreenImpl mISplitScreen;
         private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
         private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
             @Override
@@ -693,15 +711,6 @@
         };
 
         @Override
-        public ISplitScreen createExternalInterface() {
-            if (mISplitScreen != null) {
-                mISplitScreen.invalidate();
-            }
-            mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
-            return mISplitScreen;
-        }
-
-        @Override
         public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
             if (mExecutors.containsKey(listener)) return;
 
@@ -741,7 +750,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class ISplitScreenImpl extends ISplitScreen.Stub {
+    private static class ISplitScreenImpl extends ISplitScreen.Stub
+            implements ExternalInterfaceBinder {
         private SplitScreenController mController;
         private final SingleInstanceRemoteListener<SplitScreenController,
                 ISplitScreenListener> mListener;
@@ -768,7 +778,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 033d743..2dc4a04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -87,6 +87,10 @@
         return mLoggerSessionId != null;
     }
 
+    public boolean isEnterRequestedByDrag() {
+        return mEnterReason == ENTER_REASON_DRAG;
+    }
+
     /**
      * May be called before logEnter() to indicate that the session was started from a drag.
      */
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 c8dcf4a..c17f822 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
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -503,6 +505,12 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
 
+        // If split still not active, apply windows bounds first to avoid surface reset to
+        // wrong pos by SurfaceAnimator from wms.
+        if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+            updateWindowBounds(mSplitLayout, wct);
+        }
+
         wct.sendPendingIntent(intent, fillInIntent, options);
         mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
     }
@@ -1109,6 +1117,10 @@
 
     private void addActivityOptions(Bundle opts, StageTaskListener stage) {
         opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+        // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
+        // will be canceled.
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
     }
 
     void updateActivityOptions(Bundle opts, @SplitPosition int position) {
@@ -1455,18 +1467,27 @@
                 }
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
-            // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
-            onSplitScreenEnter();
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
-            mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
-            mMainStage.activate(wct, true /* includingTopTask */);
-            updateWindowBounds(mSplitLayout, wct);
-            wct.reorder(mRootTaskInfo.token, true);
-            wct.setForceTranslucent(mRootTaskInfo.token, false);
+            if (mLogger.isEnterRequestedByDrag()) {
+                prepareEnterSplitScreen(wct);
+            } else {
+                // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+                onSplitScreenEnter();
+                mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+                mMainStage.activate(wct, true /* includingTopTask */);
+                updateWindowBounds(mSplitLayout, wct);
+                wct.reorder(mRootTaskInfo.token, true);
+                wct.setForceTranslucent(mRootTaskInfo.token, false);
+            }
+
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
-                mSplitLayout.flingDividerToCenter();
+                if (mLogger.isEnterRequestedByDrag()) {
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+                } else {
+                    mSplitLayout.flingDividerToCenter();
+                }
             });
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a3..538bbec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@
  * Interface to engage starting window feature.
  */
 public interface StartingSurface {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate starting windows.
-     */
-    default IStartingWindow createExternalInterface() {
-        return null;
-    }
-
     /**
      * Returns the background color for a starting window if existing.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21..0c23f10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -43,10 +44,12 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 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.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
 /**
@@ -76,6 +79,7 @@
     private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
     private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
     private final Context mContext;
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final ShellExecutor mSplashScreenExecutor;
     /**
@@ -86,12 +90,14 @@
 
     public StartingWindowController(Context context,
             ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             ShellExecutor splashScreenExecutor,
             StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
             IconProvider iconProvider,
             TransactionPool pool) {
         mContext = context;
+        mShellController = shellController;
         mShellTaskOrganizer = shellTaskOrganizer;
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
                 iconProvider, pool);
@@ -107,8 +113,14 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IStartingWindowImpl(this);
+    }
+
     private void onInit() {
         mShellTaskOrganizer.initStartingWindow(this);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+                this::createExternalInterface, this);
     }
 
     @Override
@@ -222,17 +234,6 @@
      * The interface for calls from outside the Shell, within the host process.
      */
     private class StartingSurfaceImpl implements StartingSurface {
-        private IStartingWindowImpl mIStartingWindow;
-
-        @Override
-        public IStartingWindowImpl createExternalInterface() {
-            if (mIStartingWindow != null) {
-                mIStartingWindow.invalidate();
-            }
-            mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
-            return mIStartingWindow;
-        }
-
         @Override
         public int getBackgroundColor(TaskInfo taskInfo) {
             synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IStartingWindowImpl extends IStartingWindow.Stub {
+    private static class IStartingWindowImpl extends IStartingWindow.Stub
+            implements ExternalInterfaceBinder {
         private StartingWindowController mController;
         private SingleInstanceRemoteListener<StartingWindowController,
                 IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mController = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 5799394..fdf073f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@
 import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
 
 /**
  * Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@
     private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
             new CopyOnWriteArrayList<>();
 
+    private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+            new ArrayMap<>();
+    // References to the existing interfaces, to be invalidated when they are recreated
+    private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
     private Configuration mLastConfiguration;
 
 
@@ -67,6 +77,11 @@
         mShellInit = shellInit;
         mShellCommandHandler = shellCommandHandler;
         mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     /**
@@ -124,6 +139,47 @@
         mUserChangeListeners.remove(listener);
     }
 
+    /**
+     * Adds an interface that can be called from a remote process. This method takes a supplier
+     * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+     * will request new binder instances for each instance of Launcher that it provides binders
+     * to.
+     *
+     * @param extra the key for the interface, {@see ShellSharedConstants}
+     * @param binderSupplier the supplier of the binder to pass to the external process
+     * @param callerInstance the instance of the caller, purely for logging
+     */
+    public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+            Object callerInstance) {
+        ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+                callerInstance.getClass().getSimpleName(), extra);
+        if (mExternalInterfaceSuppliers.containsKey(extra)) {
+            throw new IllegalArgumentException("Supplier with same key already exists: "
+                    + extra);
+        }
+        mExternalInterfaceSuppliers.put(extra, binderSupplier);
+    }
+
+    /**
+     * Updates the given bundle with the set of external interfaces, invalidating the old set of
+     * binders.
+     */
+    private void createExternalInterfaces(Bundle output) {
+        // Invalidate the old binders
+        for (int i = 0; i < mExternalInterfaces.size(); i++) {
+            mExternalInterfaces.valueAt(i).invalidate();
+        }
+        mExternalInterfaces.clear();
+
+        // Create new binders for each key
+        for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+            final String key = mExternalInterfaceSuppliers.keyAt(i);
+            final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+            mExternalInterfaces.put(key, b);
+            output.putBinder(key, b.asBinder());
+        }
+    }
+
     @VisibleForTesting
     void onConfigurationChanged(Configuration newConfig) {
         // The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@
         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
         pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
         pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+        if (!mExternalInterfaces.isEmpty()) {
+            pw.println(innerPrefix + "mExternalInterfaces={");
+            for (String key : mExternalInterfaces.keySet()) {
+                pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+            }
+            pw.println(innerPrefix + "}");
+        }
     }
 
     /**
@@ -211,7 +275,6 @@
      */
     @ExternalThread
     private class ShellInterfaceImpl implements ShellInterface {
-
         @Override
         public void onInit() {
             try {
@@ -222,28 +285,6 @@
         }
 
         @Override
-        public void dump(PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to dump the Shell in 2s", e);
-            }
-        }
-
-        @Override
-        public boolean handleCommand(String[] args, PrintWriter pw) {
-            try {
-                boolean[] result = new boolean[1];
-                mMainExecutor.executeBlocking(() -> {
-                    result[0] = mShellCommandHandler.handleCommand(args, pw);
-                });
-                return result[0];
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to handle Shell command in 2s", e);
-            }
-        }
-
-        @Override
         public void onConfigurationChanged(Configuration newConfiguration) {
             mMainExecutor.execute(() ->
                     ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@
             mMainExecutor.execute(() ->
                     ShellController.this.onUserProfilesChanged(profiles));
         }
+
+        @Override
+        public boolean handleCommand(String[] args, PrintWriter pw) {
+            try {
+                boolean[] result = new boolean[1];
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = mShellCommandHandler.handleCommand(args, pw);
+                });
+                return result[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to handle Shell command in 2s", e);
+            }
+        }
+
+        @Override
+        public void createExternalInterfaces(Bundle bundle) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    ShellController.this.createExternalInterfaces(bundle);
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to get Shell command in 2s", e);
+            }
+        }
+
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to dump the Shell in 2s", e);
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c82..bc5dd11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 
@@ -37,18 +38,6 @@
     default void onInit() {}
 
     /**
-     * Dumps the shell state.
-     */
-    default void dump(PrintWriter pw) {}
-
-    /**
-     * Handles a shell command.
-     */
-    default boolean handleCommand(final String[] args, PrintWriter pw) {
-        return false;
-    }
-
-    /**
      * Notifies the Shell that the configuration has changed.
      */
     default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@
      * Notifies the Shell when a profile belonging to the user changes.
      */
     default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+    /**
+     * Handles a shell command.
+     */
+    default boolean handleCommand(final String[] args, PrintWriter pw) {
+        return false;
+    }
+
+    /**
+     * Updates the given {@param bundle} with the set of exposed interfaces.
+     */
+    default void createExternalInterfaces(Bundle bundle) {}
+
+    /**
+     * Dumps the shell state.
+     */
+    default void dump(PrintWriter pw) {}
 }
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
new file mode 100644
index 0000000..bdda6a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+    // See IPip.aidl
+    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+    // See ISplitScreen.aidl
+    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+    // See IOneHanded.aidl
+    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+    // See IShellTransitions.aidl
+    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+            "extra_shell_shell_transitions";
+    // See IStartingWindow.aidl
+    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+            "extra_shell_starting_window";
+    // See IRecentTasks.aidl
+    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";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1ae779e..91c153e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,13 +18,11 @@
 
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -43,10 +41,11 @@
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -59,6 +58,10 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -84,7 +86,6 @@
 import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.view.Choreographer;
@@ -123,21 +124,6 @@
 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
     private static final int MAX_ANIMATION_DURATION = 3000;
 
-    /**
-     * Restrict ability of activities overriding transition animation in a way such that
-     * an activity can do it only when the transition happens within a same task.
-     *
-     * @see android.app.Activity#overridePendingTransition(int, int)
-     */
-    private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
-            "persist.wm.disable_custom_task_animation";
-
-    /**
-     * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
-     */
-    static boolean sDisableCustomTaskAnimationProperty =
-            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
-
     private final TransactionPool mTransactionPool;
     private final DisplayController mDisplayController;
     private final Context mContext;
@@ -385,8 +371,10 @@
                         change.getEndAbsBounds().top - info.getRootOffset().y);
                 // Seamless display transition doesn't need to animate.
                 if (isSeamlessDisplayChange) continue;
-                if (isTask) {
-                    // Skip non-tasks since those usually have null bounds.
+                if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
+                        && !change.hasFlags(FLAG_FILLS_TASK))) {
+                    // Update Task and embedded split window crop bounds, otherwise we may see crop
+                    // on previous bounds during the rotation animation.
                     startTransaction.setWindowCrop(change.getLeash(),
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
@@ -432,22 +420,8 @@
                     cornerRadius = 0;
                 }
 
-                if (a.getShowBackdrop()) {
-                    if (info.getAnimationOptions().getBackgroundColor() != 0) {
-                        // If available use the background color provided through AnimationOptions
-                        backgroundColorForTransition =
-                                info.getAnimationOptions().getBackgroundColor();
-                    } else if (a.getBackdropColor() != 0) {
-                        // Otherwise fallback on the background color provided through the animation
-                        // definition.
-                        backgroundColorForTransition = a.getBackdropColor();
-                    } else if (change.getBackgroundColor() != 0) {
-                        // Otherwise default to the window's background color if provided through
-                        // the theme as the background color for the animation - the top most window
-                        // with a valid background color and showBackground set takes precedence.
-                        backgroundColorForTransition = change.getBackgroundColor();
-                    }
-                }
+                backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
+                        backgroundColorForTransition);
 
                 boolean delayedEdgeExtension = false;
                 if (!isTask && a.hasExtension()) {
@@ -669,29 +643,6 @@
         return edgeExtensionLayer;
     }
 
-    private void addBackgroundToTransition(
-            @NonNull SurfaceControl rootLeash,
-            @ColorInt int color,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction
-    ) {
-        final Color bgColor = Color.valueOf(color);
-        final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
-
-        final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
-                .setName("Animation Background")
-                .setParent(rootLeash)
-                .setColorLayer()
-                .setOpaque(true)
-                .build();
-
-        startTransaction
-                .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
-                .setColor(animationBackgroundSurface, colorArray)
-                .show(animationBackgroundSurface);
-        finishTransaction.remove(animationBackgroundSurface);
-    }
-
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -705,9 +656,9 @@
     }
 
     @Nullable
-    private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
-            int wallpaperTransit) {
-        Animation a = null;
+    private Animation loadAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, int wallpaperTransit) {
+        Animation a;
 
         final int type = info.getType();
         final int flags = info.getFlags();
@@ -718,12 +669,10 @@
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
-        final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
+        final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
         final Rect endBounds = Transitions.isClosingType(changeMode)
                 ? mRotator.getEndBoundsInStartRotation(change)
                 : change.getEndAbsBounds();
-        final boolean isDream =
-                isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
 
         if (info.isKeyguardGoingAway()) {
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -764,87 +713,7 @@
             // This received a transferred starting window, so don't animate
             return null;
         } else {
-            int animAttr = 0;
-            boolean translucent = false;
-            if (isDream) {
-                if (type == TRANSIT_OPEN) {
-                    animAttr = enter
-                            ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
-                            : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
-                } else if (type == TRANSIT_CLOSE) {
-                    animAttr = enter
-                            ? 0
-                            : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
-                }
-            } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
-                        : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
-            } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
-                        : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
-            } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
-                        : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-            } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
-                        : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
-            } else if (type == TRANSIT_OPEN) {
-                // We will translucent open animation for translucent activities and tasks. Choose
-                // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
-                // TransitionAnimation loads appropriate animation later.
-                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
-                    translucent = true;
-                }
-                if (isTask && !translucent) {
-                    animAttr = enter
-                            ? R.styleable.WindowAnimation_taskOpenEnterAnimation
-                            : R.styleable.WindowAnimation_taskOpenExitAnimation;
-                } else {
-                    animAttr = enter
-                            ? R.styleable.WindowAnimation_activityOpenEnterAnimation
-                            : R.styleable.WindowAnimation_activityOpenExitAnimation;
-                }
-            } else if (type == TRANSIT_TO_FRONT) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
-                        : R.styleable.WindowAnimation_taskToFrontExitAnimation;
-            } else if (type == TRANSIT_CLOSE) {
-                if (isTask) {
-                    animAttr = enter
-                            ? R.styleable.WindowAnimation_taskCloseEnterAnimation
-                            : R.styleable.WindowAnimation_taskCloseExitAnimation;
-                } else {
-                    if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
-                        translucent = true;
-                    }
-                    animAttr = enter
-                            ? R.styleable.WindowAnimation_activityCloseEnterAnimation
-                            : R.styleable.WindowAnimation_activityCloseExitAnimation;
-                }
-            } else if (type == TRANSIT_TO_BACK) {
-                animAttr = enter
-                        ? R.styleable.WindowAnimation_taskToBackEnterAnimation
-                        : R.styleable.WindowAnimation_taskToBackExitAnimation;
-            }
-
-            if (animAttr != 0) {
-                if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
-                    a = mTransitionAnimation
-                            .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
-                                    animAttr, translucent);
-                } else {
-                    a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
-                }
-            }
-
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                    "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
-                    transitTypeToString(type),
-                    enter);
+            a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
         }
 
         if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d..da39017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@
  */
 @ExternalThread
 public interface ShellTransitions {
-
-    /**
-     * Returns a binder that can be passed to an external process to manipulate remote transitions.
-     */
-    default IShellTransitions createExternalInterface() {
-        return null;
-    }
-
     /**
      * Registers a remote transition.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
new file mode 100644
index 0000000..efee6f40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
+import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Color;
+import android.os.SystemProperties;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/** The helper class that provides methods for adding styles to transition animations. */
+public class TransitionAnimationHelper {
+
+    /**
+     * Restrict ability of activities overriding transition animation in a way such that
+     * an activity can do it only when the transition happens within a same task.
+     *
+     * @see android.app.Activity#overridePendingTransition(int, int)
+     */
+    private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+            "persist.wm.disable_custom_task_animation";
+
+    /**
+     * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+     */
+    static final boolean sDisableCustomTaskAnimationProperty =
+            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
+
+    /** Loads the animation that is defined through attribute id for the given transition. */
+    @Nullable
+    public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, int wallpaperTransit,
+            @NonNull TransitionAnimation transitionAnimation) {
+        final int type = info.getType();
+        final int changeMode = change.getMode();
+        final int changeFlags = change.getFlags();
+        final boolean enter = Transitions.isOpeningType(changeMode);
+        final boolean isTask = change.getTaskInfo() != null;
+        final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+        final int overrideType = options != null ? options.getType() : ANIM_NONE;
+        final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
+        final boolean isDream =
+                isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
+        int animAttr = 0;
+        boolean translucent = false;
+        if (isDream) {
+            if (type == TRANSIT_OPEN) {
+                animAttr = enter
+                        ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
+                        : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
+            } else if (type == TRANSIT_CLOSE) {
+                animAttr = enter
+                        ? 0
+                        : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+            }
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+        } else if (type == TRANSIT_OPEN) {
+            // We will translucent open animation for translucent activities and tasks. Choose
+            // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
+            // TransitionAnimation loads appropriate animation later.
+            if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+                translucent = true;
+            }
+            if (isTask && !translucent) {
+                animAttr = enter
+                        ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+                        : R.styleable.WindowAnimation_taskOpenExitAnimation;
+            } else {
+                animAttr = enter
+                        ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+                        : R.styleable.WindowAnimation_activityOpenExitAnimation;
+            }
+        } else if (type == TRANSIT_TO_FRONT) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+                    : R.styleable.WindowAnimation_taskToFrontExitAnimation;
+        } else if (type == TRANSIT_CLOSE) {
+            if (isTask) {
+                animAttr = enter
+                        ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+                        : R.styleable.WindowAnimation_taskCloseExitAnimation;
+            } else {
+                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+                    translucent = true;
+                }
+                animAttr = enter
+                        ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+                        : R.styleable.WindowAnimation_activityCloseExitAnimation;
+            }
+        } else if (type == TRANSIT_TO_BACK) {
+            animAttr = enter
+                    ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+                    : R.styleable.WindowAnimation_taskToBackExitAnimation;
+        }
+
+        Animation a = null;
+        if (animAttr != 0) {
+            if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+                a = transitionAnimation
+                        .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+                                animAttr, translucent);
+            } else {
+                a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
+            }
+        }
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
+                transitTypeToString(type),
+                enter);
+        return a;
+    }
+
+    /**
+     * Gets the background {@link ColorInt} for the given transition animation if it is set.
+     *
+     * @param defaultColor  {@link ColorInt} to return if there is no background color specified by
+     *                      the given transition animation.
+     */
+    @ColorInt
+    public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Animation a,
+            @ColorInt int defaultColor) {
+        if (!a.getShowBackdrop()) {
+            return defaultColor;
+        }
+        if (info.getAnimationOptions() != null
+                && info.getAnimationOptions().getBackgroundColor() != 0) {
+            // If available use the background color provided through AnimationOptions
+            return info.getAnimationOptions().getBackgroundColor();
+        } else if (a.getBackdropColor() != 0) {
+            // Otherwise fallback on the background color provided through the animation
+            // definition.
+            return a.getBackdropColor();
+        } else if (change.getBackgroundColor() != 0) {
+            // Otherwise default to the window's background color if provided through
+            // the theme as the background color for the animation - the top most window
+            // with a valid background color and showBackground set takes precedence.
+            return change.getBackgroundColor();
+        }
+        return defaultColor;
+    }
+
+    /**
+     * Adds the given {@code backgroundColor} as the background color to the transition animation.
+     */
+    public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash,
+            @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        if (backgroundColor == 0) {
+            // No background color.
+            return;
+        }
+        final Color bgColor = Color.valueOf(backgroundColor);
+        final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
+        final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder()
+                .setName("Animation Background")
+                .setParent(rootLeash)
+                .setColorLayer()
+                .setOpaque(true)
+                .build();
+        startTransaction
+                .setLayer(animationBackgroundSurface, Integer.MIN_VALUE)
+                .setColor(animationBackgroundSurface, colorArray)
+                .show(animationBackgroundSurface);
+        finishTransaction.remove(animationBackgroundSurface);
+    }
+}
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 d2e8624..aaaccd8 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
@@ -25,10 +25,12 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -61,11 +63,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
@@ -115,6 +119,7 @@
     private final DefaultTransitionHandler mDefaultTransitionHandler;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
     private final DisplayController mDisplayController;
+    private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -142,6 +147,7 @@
 
     public Transitions(@NonNull Context context,
             @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
             @NonNull WindowOrganizer organizer,
             @NonNull TransactionPool pool,
             @NonNull DisplayController displayController,
@@ -156,10 +162,14 @@
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
                 displayController, pool, mainExecutor, mainHandler, animExecutor);
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+        mShellController = shellController;
         shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+                this::createExternalInterface, this);
+
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -193,6 +203,10 @@
         return mImpl;
     }
 
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IShellTransitionsImpl(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
@@ -441,31 +455,34 @@
             return;
         }
 
-        // 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.
         final int changeSize = info.getChanges().size();
-        if (changeSize == 2) {
-            boolean nonTaskChange = true;
-            boolean transferStartingWindow = false;
-            for (int i = changeSize - 1; i >= 0; --i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                if (change.getTaskInfo() != null) {
-                    nonTaskChange = false;
-                    break;
-                }
-                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                    transferStartingWindow = true;
-                }
+        boolean taskChange = false;
+        boolean transferStartingWindow = false;
+        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.hasFlags(FLAG_IS_OCCLUDED)) {
+                allOccluded = false;
             }
-            if (nonTaskChange && transferStartingWindow) {
-                t.apply();
-                finishT.apply();
-                // Treat this as an abort since we are bypassing any merge logic and effectively
-                // finishing immediately.
-                onAbort(transitionToken);
-                return;
-            }
+        }
+        // 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
+                // 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)
+                && allOccluded)) {
+            t.apply();
+            finishT.apply();
+            // Treat this as an abort since we are bypassing any merge logic and effectively
+            // finishing immediately.
+            onAbort(transitionToken);
+            return;
         }
 
         final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -542,6 +559,22 @@
                 "This shouldn't happen, maybe the default handler is broken.");
     }
 
+    /**
+     * Gives every handler (in order) a chance to handle request until one consumes the transition.
+     * @return the WindowContainerTransaction given by the handler which consumed the transition.
+     */
+    public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+        for (int i = mHandlers.size() - 1; i >= 0; --i) {
+            if (mHandlers.get(i) == skip) continue;
+            WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+            if (wct != null) {
+                return wct;
+            }
+        }
+        return null;
+    }
+
     /** Special version of finish just for dealing with no-op/invalid transitions. */
     private void onAbort(IBinder transition) {
         onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -716,8 +749,8 @@
                         null /* newDisplayAreaInfo */);
             }
         }
-        active.mToken = mOrganizer.startTransition(
-                request.getType(), transitionToken, wct);
+        mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
+        active.mToken = transitionToken;
         mActiveTransitions.add(active);
     }
 
@@ -726,7 +759,7 @@
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
         final ActiveTransition active = new ActiveTransition();
         active.mHandler = handler;
-        active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
+        active.mToken = mOrganizer.startNewTransition(type, wct);
         mActiveTransitions.add(active);
         return active.mToken;
     }
@@ -897,17 +930,6 @@
      */
     @ExternalThread
     private class ShellTransitionImpl implements ShellTransitions {
-        private IShellTransitionsImpl mIShellTransitions;
-
-        @Override
-        public IShellTransitions createExternalInterface() {
-            if (mIShellTransitions != null) {
-                mIShellTransitions.invalidate();
-            }
-            mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
-            return mIShellTransitions;
-        }
-
         @Override
         public void registerRemote(@NonNull TransitionFilter filter,
                 @NonNull RemoteTransition remoteTransition) {
@@ -928,7 +950,8 @@
      * The interface for calls from outside the host process.
      */
     @BinderThread
-    private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+    private static class IShellTransitionsImpl extends IShellTransitions.Stub
+            implements ExternalInterfaceBinder {
         private Transitions mTransitions;
 
         IShellTransitionsImpl(Transitions transitions) {
@@ -938,7 +961,8 @@
         /**
          * Invalidates this instance, preventing future calls from updating the controller.
          */
-        void invalidate() {
+        @Override
+        public void invalidate() {
             mTransitions = null;
         }
 
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 e8a2cb160..3df33f3 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+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_UNDEFINED;
@@ -25,6 +26,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -36,7 +38,8 @@
 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.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.transition.Transitions;
 
@@ -44,7 +47,7 @@
  * View model for the window decoration with a caption and shadows. Works with
  * {@link CaptionWindowDecoration}.
  */
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -53,6 +56,9 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
+    private DesktopModeController mDesktopModeController;
+
+    private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -60,7 +66,8 @@
             Choreographer mainChoreographer,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            DesktopModeController desktopModeController) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -68,6 +75,7 @@
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
+        mDesktopModeController = desktopModeController;
     }
 
     @Override
@@ -76,12 +84,12 @@
     }
 
     @Override
-    public CaptionWindowDecoration createWindowDecoration(
+    public boolean createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        if (!shouldShowWindowDecor(taskInfo)) return null;
+        if (!shouldShowWindowDecor(taskInfo)) return false;
         final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
                 mContext,
                 mDisplayController,
@@ -91,30 +99,24 @@
                 mMainHandler,
                 mMainChoreographer,
                 mSyncQueue);
+        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
         TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
         CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
-        setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
+        setupWindowDecorationForTransition(taskInfo, startT, finishT);
         setupCaptionColor(taskInfo, windowDecoration);
-        return windowDecoration;
+        return true;
     }
 
     @Override
-    public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
-        if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
-        final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
-        if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
-            return null;
-        }
-        return captionWindowDecor;
-    }
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) return;
 
-    @Override
-    public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
         decoration.relayout(taskInfo);
-
         setupCaptionColor(taskInfo, decoration);
     }
 
@@ -127,11 +129,22 @@
     public void setupWindowDecorationForTransition(
             RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            CaptionWindowDecoration decoration) {
+            SurfaceControl.Transaction finishT) {
+        final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) return;
+
         decoration.relayout(taskInfo, startT, finishT);
     }
 
+    @Override
+    public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+        final CaptionWindowDecoration decoration =
+                mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+        if (decoration == null) return;
+
+        decoration.close();
+    }
+
     private class CaptionTouchEventListener implements
             View.OnClickListener, View.OnTouchListener {
 
@@ -210,8 +223,10 @@
         }
 
         private void handleEventForMove(MotionEvent e) {
-            if (mTaskOrganizer.getRunningTaskInfo(mTaskId).getWindowingMode()
-                    == WINDOWING_MODE_FULLSCREEN) {
+            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            int windowingMode =  mDesktopModeController
+                    .getDisplayAreaWindowingMode(taskInfo.displayId);
+            if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
                 return;
             }
             switch (e.getActionMasked()) {
@@ -229,8 +244,14 @@
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
+                            .stableInsets().top;
                     mDragResizeCallback.onDragResizeEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+                    if (e.getRawY(dragPointerIdx) <= statusBarHeight
+                            && windowingMode == WINDOWING_MODE_FREEFORM) {
+                        mDesktopModeController.setDesktopModeActive(false);
+                    }
                     break;
                 }
             }
@@ -239,7 +260,8 @@
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
-        return DesktopMode.IS_SUPPORTED
+        return DesktopModeStatus.IS_SUPPORTED
+                && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
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 5040bc3..733f6b7 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
@@ -34,7 +34,7 @@
 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.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -164,7 +164,7 @@
         View caption = mResult.mRootView.findViewById(R.id.caption);
         caption.setOnTouchListener(mOnCaptionTouchListener);
         View maximize = caption.findViewById(R.id.maximize_window);
-        if (DesktopMode.IS_SUPPORTED) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             // Hide maximize button when desktop mode is available
             maximize.setVisibility(View.GONE);
         } else {
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 d9697d2..d7f71c8 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
@@ -19,8 +19,6 @@
 import android.app.ActivityManager;
 import android.view.SurfaceControl;
 
-import androidx.annotation.Nullable;
-
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 
 /**
@@ -28,10 +26,8 @@
  * customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
  * interactions with UI widgets in window decorations and send corresponding requests to system
  * servers.
- *
- * @param <T> The actual decoration type
  */
-public interface WindowDecorViewModel<T extends AutoCloseable> {
+public interface WindowDecorViewModel {
 
     /**
      * Sets the transition starter that starts freeform task transitions.
@@ -50,29 +46,19 @@
      * @param finishT the finish transaction to restore states after the transition
      * @return the window decoration object
      */
-    @Nullable T createWindowDecoration(
+    boolean createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT);
 
     /**
-     * Adopts the window decoration if possible.
-     * May be {@code null} if a window decor is not needed or the given one is incompatible.
-     *
-     * @param windowDecor the potential window decoration to adopt
-     * @return the window decoration if it can be adopted, or {@code null} otherwise.
-     */
-    @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
-
-    /**
      * Notifies a task info update on the given task, with the window decoration created previously
      * for this task by {@link #createWindowDecoration}.
      *
      * @param taskInfo the new task info of the task
-     * @param windowDecoration the window decoration created for the task
      */
-    void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+    void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
 
     /**
      * Notifies a transition is about to start about the given task to give the window decoration a
@@ -80,11 +66,16 @@
      *
      * @param startT the start transaction to be applied before the transition
      * @param finishT the finish transaction to restore states after the transition
-     * @param windowDecoration the window decoration created for the task
      */
     void setupWindowDecorationForTransition(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            T windowDecoration);
+            SurfaceControl.Transaction finishT);
+
+    /**
+     * Destroys the window decoration of the give task.
+     *
+     * @param taskInfo the info of the task
+     */
+    void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 3ca5b9c..d6adaa7 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -48,6 +48,6 @@
         "wm-flicker-common-assertions",
         "wm-flicker-common-app-helpers",
         "platform-test-annotations",
-        "wmshell-flicker-test-components",
+        "flickertestapplib",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 574a9f4..2d6e8f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,13 +13,15 @@
         <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" />
         <!-- restart launcher to activate TAPL -->
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
     </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="WMShellFlickerTestApp.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"/>
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 2b162ae..6370df4 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
@@ -32,55 +32,52 @@
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
 
 /**
- * Base test class containing common assertions for [ComponentMatcher.NAV_BAR],
- * [ComponentMatcher.TASK_BAR], [ComponentMatcher.STATUS_BAR], and general assertions
+ * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
+ * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions
  * (layers visible in consecutive states, entire screen covered, etc.)
  */
-abstract class BaseTest @JvmOverloads constructor(
+abstract class BaseTest
+@JvmOverloads
+constructor(
     protected val testSpec: FlickerTestParameter,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(
-                instrumentation,
-                clearCacheAfterParsing = false
-            ).currentState.wmState.isTablet
+            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
+                .currentState
+                .wmState
+                .isTablet
         )
     }
 
-    /**
-     * Specification of the test transition to execute
-     */
+    /** Specification of the test transition to execute */
     abstract val transition: FlickerBuilder.() -> Unit
 
     /**
-     * Entry point for the test runner. It will use this method to initialize and cache
-     * flicker executions
+     * Entry point for the test runner. It will use this method to initialize and cache flicker
+     * executions
      */
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            setup {
-                testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet)
-            }
+            setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
             transition()
         }
     }
 
-    /**
-     * Checks that all parts of the screen are covered during the transition
-     */
-    open fun entireScreenCovered() = testSpec.entireScreenCovered()
+    /** Checks that all parts of the screen are covered during the transition */
+    @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] layer is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
      */
     @Presubmit
     @Test
@@ -90,7 +87,8 @@
     }
 
     /**
-     * Checks the position of the [ComponentMatcher.NAV_BAR] at the start and end of the transition
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
      */
     @Presubmit
     @Test
@@ -100,7 +98,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
      *
      * Note: Phones only
      */
@@ -112,7 +110,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] layer is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
      */
     @Presubmit
     @Test
@@ -122,7 +120,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
      *
      * Note: Large screen only
      */
@@ -134,7 +132,8 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+     * transition
      */
     @Presubmit
     @Test
@@ -142,40 +141,38 @@
         testSpec.statusBarLayerIsVisibleAtStartAndEnd()
 
     /**
-     * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the transition
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
      */
     @Presubmit
     @Test
     open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
      */
     @Presubmit
     @Test
     open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that all layers that are visible on the trace, are visible for at least 2
-     * consecutive entries.
+     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
      */
     @Presubmit
     @Test
     open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers {
-            this.visibleLayersShownMoreThanOneConsecutiveEntry()
-        }
+        testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
     }
 
     /**
-     * Checks that all windows that are visible on the trace, are visible for at least 2
-     * consecutive entries.
+     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
      */
     @Presubmit
     @Test
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm {
-            this.visibleWindowsShownMoreThanOneConsecutiveEntry()
-        }
+        testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
 }
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 5597990..6f1ff99 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
@@ -15,27 +15,23 @@
  */
 
 @file:JvmName("CommonAssertions")
+
 package com.android.wm.shell.flicker
 
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.common.region.Region
 
 fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
-    assertLayersEnd {
-        this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
-    }
+    assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
 fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
-    assertLayersEnd {
-        this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
-    }
+    assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
 fun FlickerTestParameter.appPairsDividerBecomesVisible() {
@@ -46,6 +42,58 @@
     }
 }
 
+fun FlickerTestParameter.splitScreenEntered(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    fromOtherApp: Boolean
+) {
+    if (fromOtherApp) {
+        appWindowIsInvisibleAtStart(component1)
+    } else {
+        appWindowIsVisibleAtStart(component1)
+    }
+    appWindowIsInvisibleAtStart(component2)
+    splitScreenDividerIsInvisibleAtStart()
+
+    appWindowIsVisibleAtEnd(component1)
+    appWindowIsVisibleAtEnd(component2)
+    splitScreenDividerIsVisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDismissed(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    toHome: Boolean
+) {
+    appWindowIsVisibleAtStart(component1)
+    appWindowIsVisibleAtStart(component2)
+    splitScreenDividerIsVisibleAtStart()
+
+    appWindowIsInvisibleAtEnd(component1)
+    if (toHome) {
+        appWindowIsInvisibleAtEnd(component2)
+    } else {
+        appWindowIsVisibleAtEnd(component2)
+    }
+    splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+    assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+    assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+    assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+    assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
 fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
@@ -58,40 +106,20 @@
     }
 }
 
-fun FlickerTestParameter.layerBecomesVisible(
-    component: IComponentMatcher
-) {
-    assertLayers {
-        this.isInvisible(component)
-            .then()
-            .isVisible(component)
-    }
+fun FlickerTestParameter.layerBecomesVisible(component: IComponentMatcher) {
+    assertLayers { this.isInvisible(component).then().isVisible(component) }
 }
 
-fun FlickerTestParameter.layerBecomesInvisible(
-    component: IComponentMatcher
-) {
-    assertLayers {
-        this.isVisible(component)
-            .then()
-            .isInvisible(component)
-    }
+fun FlickerTestParameter.layerBecomesInvisible(component: IComponentMatcher) {
+    assertLayers { this.isVisible(component).then().isInvisible(component) }
 }
 
-fun FlickerTestParameter.layerIsVisibleAtEnd(
-    component: IComponentMatcher
-) {
-    assertLayersEnd {
-        this.isVisible(component)
-    }
+fun FlickerTestParameter.layerIsVisibleAtEnd(component: IComponentMatcher) {
+    assertLayersEnd { this.isVisible(component) }
 }
 
-fun FlickerTestParameter.layerKeepVisible(
-    component: IComponentMatcher
-) {
-    assertLayers {
-        this.isVisible(component)
-    }
+fun FlickerTestParameter.layerKeepVisible(component: IComponentMatcher) {
+    assertLayers { this.isVisible(component) }
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
@@ -105,33 +133,22 @@
             .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
             .then()
             .splitAppLayerBoundsSnapToDivider(
-                component, landscapePosLeft, portraitPosTop, endRotation)
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                endRotation
+            )
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(
-    component: IComponentMatcher
-) {
+fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
     assertLayers {
-        if (isShellTransitionsEnabled) {
-            this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-                .then()
-                .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-                .then()
-                // TODO(b/245472831): Verify the component should snap to divider.
-                .isVisible(component)
-        } else {
-            this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-                .then()
-                .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-                .then()
-                // TODO(b/245472831): Verify the component should snap to divider.
-                .isVisible(component)
-                .then()
-                .isInvisible(component, isOptional = true)
-                .then()
-                .isVisible(component)
-        }
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
+            .then()
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+            .then()
+            // TODO(b/245472831): Verify the component should snap to divider.
+            .isVisible(component)
     }
 }
 
@@ -142,7 +159,11 @@
 ) {
     assertLayers {
         this.splitAppLayerBoundsSnapToDivider(
-                component, landscapePosLeft, portraitPosTop, endRotation)
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                endRotation
+            )
             .then()
             .isVisible(component, true)
             .then()
@@ -178,15 +199,27 @@
     assertLayers {
         if (landscapePosLeft) {
             this.splitAppLayerBoundsSnapToDivider(
-                component, landscapePosLeft, portraitPosTop, endRotation)
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                endRotation
+            )
         } else {
             this.splitAppLayerBoundsSnapToDivider(
-                component, landscapePosLeft, portraitPosTop, endRotation)
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    endRotation
+                )
                 .then()
                 .isInvisible(component)
                 .then()
                 .splitAppLayerBoundsSnapToDivider(
-                    component, landscapePosLeft, portraitPosTop, endRotation)
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    endRotation
+                )
         }
     }
 }
@@ -211,45 +244,46 @@
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return invoke {
         val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(component).coversAtMost(
-            if (displayBounds.width > displayBounds.height) {
-                if (landscapePosLeft) {
-                    Region.from(
-                        0,
-                        0,
-                        (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-                        displayBounds.bounds.bottom)
+        visibleRegion(component)
+            .coversAtMost(
+                if (displayBounds.width > displayBounds.height) {
+                    if (landscapePosLeft) {
+                        Region.from(
+                            0,
+                            0,
+                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                            displayBounds.bounds.bottom
+                        )
+                    } else {
+                        Region.from(
+                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                            0,
+                            displayBounds.bounds.right,
+                            displayBounds.bounds.bottom
+                        )
+                    }
                 } else {
-                    Region.from(
-                        (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-                        0,
-                        displayBounds.bounds.right,
-                        displayBounds.bounds.bottom
-                    )
+                    if (portraitPosTop) {
+                        Region.from(
+                            0,
+                            0,
+                            displayBounds.bounds.right,
+                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2
+                        )
+                    } else {
+                        Region.from(
+                            0,
+                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+                            displayBounds.bounds.right,
+                            displayBounds.bounds.bottom
+                        )
+                    }
                 }
-            } else {
-                if (portraitPosTop) {
-                    Region.from(
-                        0,
-                        0,
-                        displayBounds.bounds.right,
-                        (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
-                } else {
-                    Region.from(
-                        0,
-                        (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
-                        displayBounds.bounds.right,
-                        displayBounds.bounds.bottom
-                    )
-                }
-            }
-        )
+            )
     }
 }
 
-fun FlickerTestParameter.appWindowBecomesVisible(
-    component: IComponentMatcher
-) {
+fun FlickerTestParameter.appWindowBecomesVisible(component: IComponentMatcher) {
     assertWm {
         this.isAppWindowInvisible(component)
             .then()
@@ -261,36 +295,32 @@
     }
 }
 
-fun FlickerTestParameter.appWindowBecomesInvisible(
-    component: IComponentMatcher
-) {
-    assertWm {
-        this.isAppWindowVisible(component)
-            .then()
-            .isAppWindowInvisible(component)
-    }
+fun FlickerTestParameter.appWindowBecomesInvisible(component: IComponentMatcher) {
+    assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
 }
 
-fun FlickerTestParameter.appWindowIsVisibleAtEnd(
-    component: IComponentMatcher
-) {
-    assertWmEnd {
-        this.isAppWindowVisible(component)
-    }
+fun FlickerTestParameter.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+    assertWmStart { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTestParameter.appWindowKeepVisible(
-    component: IComponentMatcher
-) {
-    assertWm {
-        this.isAppWindowVisible(component)
-    }
+fun FlickerTestParameter.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+    assertWmEnd { this.isAppWindowVisible(component) }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+    assertWmStart { this.isAppWindowInvisible(component) }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+    assertWmEnd { this.isAppWindowInvisible(component) }
+}
+
+fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
+    assertWm { this.isAppWindowVisible(component) }
 }
 
 fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
-    assertLayersEnd {
-        this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
-    }
+    assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
 fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
@@ -310,9 +340,7 @@
 }
 
 fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
-    assertLayersEnd {
-        this.notContains(DOCKED_STACK_DIVIDER_COMPONENT)
-    }
+    assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
 fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
@@ -321,8 +349,7 @@
 ) {
     assertLayersEnd {
         val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(primaryComponent)
-            .overlaps(getPrimaryRegion(dividerRegion, rotation))
+        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -332,8 +359,7 @@
 ) {
     assertLayersEnd {
         val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(primaryComponent)
-            .overlaps(getPrimaryRegion(dividerRegion, rotation))
+        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -343,8 +369,7 @@
 ) {
     assertLayersEnd {
         val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(secondaryComponent)
-            .overlaps(getSecondaryRegion(dividerRegion, rotation))
+        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -354,8 +379,7 @@
 ) {
     assertLayersEnd {
         val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(secondaryComponent)
-            .overlaps(getSecondaryRegion(dividerRegion, rotation))
+        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -363,12 +387,16 @@
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
         Region.from(
-            0, 0, displayBounds.bounds.right,
+            0,
+            0,
+            displayBounds.bounds.right,
             dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
         )
     } else {
         Region.from(
-            0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+            0,
+            0,
+            dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.bottom
         )
     }
@@ -378,13 +406,17 @@
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
         Region.from(
-            0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
-            displayBounds.bounds.right, displayBounds.bounds.bottom
+            0,
+            dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+            displayBounds.bounds.right,
+            displayBounds.bounds.bottom
         )
     } else {
         Region.from(
-            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
-            displayBounds.bounds.right, displayBounds.bounds.bottom
+            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
+            0,
+            displayBounds.bounds.right,
+            displayBounds.bounds.bottom
         )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 53dd8b0..7997892 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -15,6 +15,7 @@
  */
 
 @file:JvmName("CommonConstants")
+
 package com.android.wm.shell.flicker
 
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -26,5 +27,8 @@
 val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
 
 enum class Direction {
-    UP, DOWN, LEFT, RIGHT
+    UP,
+    DOWN,
+    LEFT,
+    RIGHT
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
new file mode 100644
index 0000000..87b94ff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.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.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import java.io.IOException
+
+object MultiWindowUtils {
+    private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+        try {
+            SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+        }
+    }
+
+    fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+        Settings.Global.getInt(
+            context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+        )
+
+    fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+        Settings.Global.putInt(
+            context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+            configValue
+        )
+
+    fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+        executeShellCommand(
+            instrumentation,
+            createConfigSupportsNonResizableMultiWindowCommand(configValue)
+        )
+
+    fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+        executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+    private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+        "wm set-multi-window-config --supportsNonResizable $configValue"
+
+    private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
index 51f7a18..e0ef924 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
@@ -51,7 +51,7 @@
 
         private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
         private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
-                "cmd notification disallow_listener %s"
+            "cmd notification disallow_listener %s"
         private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener"
 
         private var instance: NotificationListener? = null
@@ -79,25 +79,23 @@
         ): StatusBarNotification? {
             instance?.run {
                 return notifications.values.firstOrNull(predicate)
-            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+            }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
         }
 
         fun waitForNotificationToAppear(
             predicate: (StatusBarNotification) -> Boolean
         ): StatusBarNotification? {
             instance?.let {
-                return waitForResult(extractor = {
-                    it.notifications.values.firstOrNull(predicate)
-                }).second
-            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+                return waitForResult(extractor = { it.notifications.values.firstOrNull(predicate) })
+                    .second
+            }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
         }
 
-        fun waitForNotificationToDisappear(
-            predicate: (StatusBarNotification) -> Boolean
-        ): Boolean {
-            return instance?.let {
-                wait { it.notifications.values.none(predicate) }
-            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+        fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean {
+            return instance?.let { wait { it.notifications.values.none(predicate) } }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
index 4d87ec9..556cb06 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
@@ -15,6 +15,7 @@
  */
 
 @file:JvmName("WaitUtils")
+
 package com.android.wm.shell.flicker
 
 import android.os.SystemClock
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 1390334..0fc2004 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
@@ -19,6 +19,7 @@
 import android.app.INotificationManager
 import android.app.NotificationManager
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.ServiceManager
 import android.view.Surface
 import androidx.test.uiautomator.By
@@ -28,26 +29,26 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
 import org.junit.runners.Parameterized
 
-/**
- * Base configurations for Bubble flicker tests
- */
-abstract class BaseBubbleScreen(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+/** Base configurations for Bubble flicker tests */
+abstract class BaseBubbleScreen(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
 
     protected val context: Context = instrumentation.context
     protected val testApp = LaunchBubbleHelper(instrumentation)
 
-    private val notifyManager = INotificationManager.Stub.asInterface(
-            ServiceManager.getService(Context.NOTIFICATION_SERVICE))
+    private val notifyManager =
+        INotificationManager.Stub.asInterface(
+            ServiceManager.getService(Context.NOTIFICATION_SERVICE)
+        )
 
-    private val uid = context.packageManager.getApplicationInfo(
-            testApp.`package`, 0).uid
+    private val uid =
+        context.packageManager
+            .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0))
+            .uid
 
     @JvmOverloads
     protected open fun buildTransition(
@@ -55,16 +56,22 @@
     ): FlickerBuilder.() -> Unit {
         return {
             setup {
-                notifyManager.setBubblesAllowed(testApp.`package`,
-                    uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
+                notifyManager.setBubblesAllowed(
+                    testApp.`package`,
+                    uid,
+                    NotificationManager.BUBBLE_PREFERENCE_ALL
+                )
                 testApp.launchViaIntent(wmHelper)
                 waitAndGetAddBubbleBtn()
                 waitAndGetCancelAllBtn()
             }
 
             teardown {
-                notifyManager.setBubblesAllowed(testApp.`package`,
-                    uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
+                notifyManager.setBubblesAllowed(
+                    testApp.`package`,
+                    uid,
+                    NotificationManager.BUBBLE_PREFERENCE_NONE
+                )
                 testApp.exit()
             }
 
@@ -72,17 +79,17 @@
         }
     }
 
-    protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject(
-            By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
-    protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject(
-            By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
+    protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? =
+        device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+    protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? =
+        device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
 
         const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index ac4de47..ab72117 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -27,7 +27,6 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,12 +38,13 @@
  * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
  *
  * Actions:
+ * ```
  *     Dismiss a bubble notification
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
@@ -60,11 +60,11 @@
             transitions {
                 wm.run { wm.defaultDisplay.getMetrics(displaySize) }
                 val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels)
-                val showBubble = device.wait(
-                    Until.findObject(
-                        By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
-                    ), FIND_OBJECT_TIMEOUT
-                )
+                val showBubble =
+                    device.wait(
+                        Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                        FIND_OBJECT_TIMEOUT
+                    )
                 showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found")
             }
         }
@@ -72,22 +72,18 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 7807854..226eab8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -22,7 +22,6 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,14 +33,15 @@
  * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
  *
  * Actions:
+ * ```
  *     Launch an app and enable app's bubble notification
  *     Send a bubble notification
  *     The activity for the bubble is launched
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     /** {@inheritDoc} */
@@ -52,11 +52,11 @@
                 addBubbleBtn?.click() ?: error("Add Bubble not found")
             }
             transitions {
-                val showBubble = device.wait(
-                    Until.findObject(
-                        By.res("com.android.systemui", "bubble_view")
-                    ), FIND_OBJECT_TIMEOUT
-                )
+                val showBubble =
+                    device.wait(
+                        Until.findObject(By.res("com.android.systemui", "bubble_view")),
+                        FIND_OBJECT_TIMEOUT
+                    )
                 showBubble?.run { showBubble.click() } ?: error("Bubble notify not found")
             }
         }
@@ -64,8 +64,6 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 49681e1..47167b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -25,7 +25,6 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,12 +36,13 @@
  * To run this test: `atest WMShellFlickerTests:LaunchBubbleFromLockScreen`
  *
  * Actions:
+ * ```
  *     Launch an bubble from notification on lock screen
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     /** {@inheritDoc} */
@@ -52,36 +52,32 @@
                 val addBubbleBtn = waitAndGetAddBubbleBtn()
                 addBubbleBtn?.click() ?: error("Bubble widget not found")
                 device.sleep()
-                wmHelper.StateSyncBuilder()
-                    .withoutTopVisibleAppWindows()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
                 device.wakeUp()
             }
             transitions {
                 // Swipe & wait for the notification shade to expand so all can be seen
-                val wm = context.getSystemService(WindowManager::class.java)
-                    ?: error("Unable to obtain WM service")
+                val wm =
+                    context.getSystemService(WindowManager::class.java)
+                        ?: error("Unable to obtain WM service")
                 val metricInsets = wm.currentWindowMetrics.windowInsets
-                val insets = metricInsets.getInsetsIgnoringVisibility(
-                    WindowInsets.Type.statusBars()
-                        or WindowInsets.Type.displayCutout()
-                )
+                val insets =
+                    metricInsets.getInsetsIgnoringVisibility(
+                        WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                    )
                 device.swipe(100, insets.top + 100, 100, device.displayHeight / 2, 4)
                 device.waitForIdle(2000)
                 instrumentation.uiAutomation.syncInputTransactions()
 
-                val notification = device.wait(
-                    Until.findObject(
-                        By.text("BubbleChat")
-                    ), FIND_OBJECT_TIMEOUT
-                )
+                val notification =
+                    device.wait(Until.findObject(By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
                 notification?.click() ?: error("Notification not found")
                 instrumentation.uiAutomation.syncInputTransactions()
-                val showBubble = device.wait(
-                    Until.findObject(
-                        By.res("com.android.systemui", "bubble_view")
-                    ), FIND_OBJECT_TIMEOUT
-                )
+                val showBubble =
+                    device.wait(
+                        Until.findObject(By.res("com.android.systemui", "bubble_view")),
+                        FIND_OBJECT_TIMEOUT
+                    )
                 showBubble?.click() ?: error("Bubble notify not found")
                 instrumentation.uiAutomation.syncInputTransactions()
                 val cancelAllBtn = waitAndGetCancelAllBtn()
@@ -92,9 +88,7 @@
     @FlakyTest(bugId = 242088970)
     @Test
     fun testAppIsVisibleAtEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayersEnd { this.isVisible(testApp) }
     }
 
     /** {@inheritDoc} */
@@ -106,32 +100,27 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 242088970)
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 242088970)
@@ -142,18 +131,22 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 242088970)
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 242088970)
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 242088970)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 251217773)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index effd330..b865999 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.bubble
 
-import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,13 +32,14 @@
  * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
  *
  * Actions:
+ * ```
  *     Launch an app and enable app's bubble notification
  *     Send a bubble notification
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     /** {@inheritDoc} */
@@ -47,14 +48,17 @@
             transitions {
                 val addBubbleBtn = waitAndGetAddBubbleBtn()
                 addBubbleBtn?.click() ?: error("Bubble widget not found")
+
+                device.wait(
+                    Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                    FIND_OBJECT_TIMEOUT
+                )
+                    ?: error("No bubbles found")
             }
         }
 
-    @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index fac0f73..bf4d7d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -24,7 +24,6 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
@@ -39,12 +38,13 @@
  * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
  *
  * Actions:
+ * ```
  *     Switch in different bubble notifications
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
 
     @Before
@@ -61,20 +61,22 @@
                     addBubbleBtn.click()
                     SystemClock.sleep(1000)
                 }
-                val showBubble = device.wait(
-                    Until.findObject(
-                        By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
-                    ), FIND_OBJECT_TIMEOUT
-                ) ?: error("Show bubble not found")
+                val showBubble =
+                    device.wait(
+                        Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                        FIND_OBJECT_TIMEOUT
+                    )
+                        ?: error("Show bubble not found")
                 showBubble.click()
                 SystemClock.sleep(1000)
             }
             transitions {
-                val bubbles: List<UiObject2> = device.wait(
-                    Until.findObjects(
-                        By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
-                    ), FIND_OBJECT_TIMEOUT
-                ) ?: error("No bubbles found")
+                val bubbles: List<UiObject2> =
+                    device.wait(
+                        Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                        FIND_OBJECT_TIMEOUT
+                    )
+                        ?: error("No bubbles found")
                 for (entry in bubbles) {
                     entry.click()
                     SystemClock.sleep(1000)
@@ -85,8 +87,6 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
index 971097d..57adeab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
 import org.junit.Before
@@ -30,13 +29,11 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
 @FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(
-    testSpec: FlickerTestParameter
-) : MultiBubblesScreen(testSpec) {
+class MultiBubblesScreenShellTransit(testSpec: FlickerTestParameter) :
+    MultiBubblesScreen(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
deleted file mode 100644
index 01ba990..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ /dev/null
@@ -1,67 +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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.util.Log
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import java.io.IOException
-
-abstract class BaseAppHelper(
-    instrumentation: Instrumentation,
-    launcherName: String,
-    component: IComponentNameMatcher
-) : StandardAppHelper(
-    instrumentation,
-    launcherName,
-    component,
-    LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
-) {
-    private val appSelector = By.pkg(`package`).depth(0)
-
-    protected val isTelevision: Boolean
-        get() = context.packageManager.run {
-            hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
-        }
-
-    val ui: UiObject2?
-        get() = uiDevice.findObject(appSelector)
-
-    fun waitUntilClosed(): Boolean {
-        return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
-    }
-
-    companion object {
-        private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
-
-        fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
-            try {
-                SystemUtil.runShellCommand(instrumentation, cmd)
-            } catch (e: IOException) {
-                Log.e("BaseAppHelper", "executeShellCommand error! $e")
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
deleted file mode 100644
index 471e010..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
+++ /dev/null
@@ -1,27 +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.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.FixedActivity.LABEL,
-    Components.FixedActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
deleted file mode 100644
index 2e690de..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ /dev/null
@@ -1,76 +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.flicker.helpers
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.testapp.Components
-
-open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.ImeActivity.LABEL,
-    Components.ImeActivity.COMPONENT.toFlickerComponent()
-) {
-    /**
-     * Opens the IME and wait for it to be displayed
-     *
-     * @param wmHelper Helper used to wait for WindowManager states
-     */
-    open fun openIME(wmHelper: WindowManagerStateHelper) {
-        if (!isTelevision) {
-            val editText = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "plain_text_input")),
-                FIND_TIMEOUT)
-
-            require(editText != null) {
-                "Text field not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
-            }
-            editText.click()
-            wmHelper.StateSyncBuilder()
-                .withImeShown()
-                .waitForAndVerify()
-        } else {
-            // If we do the same thing as above - editText.click() - on TV, that's going to force TV
-            // into the touch mode. We really don't want that.
-            launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME)
-        }
-    }
-
-    /**
-     * Opens the IME and wait for it to be gone
-     *
-     * @param wmHelper Helper used to wait for WindowManager states
-     */
-    open fun closeIME(wmHelper: WindowManagerStateHelper) {
-        if (!isTelevision) {
-            uiDevice.pressBack()
-            // Using only the AccessibilityInfo it is not possible to identify if the IME is active
-            wmHelper.StateSyncBuilder()
-                .withImeGone()
-                .waitForAndVerify()
-        } else {
-            // While pressing the back button should close the IME on TV as well, it may also lead
-            // to the app closing. So let's instead just ask the app to close the IME.
-            launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
deleted file mode 100644
index 1b8a44b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
+++ /dev/null
@@ -1,32 +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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.LaunchBubbleActivity.LABEL,
-    Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
-) {
-
-    companion object {
-        const val TIMEOUT_MS = 3_000L
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
deleted file mode 100644
index 245a82f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ /dev/null
@@ -1,52 +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.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Context
-import android.provider.Settings
-import com.android.server.wm.traces.common.ComponentNameMatcher
-
-class MultiWindowHelper(
-    instrumentation: Instrumentation,
-    activityLabel: String,
-    componentsInfo: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
-
-    companion object {
-        fun getDevEnableNonResizableMultiWindow(context: Context): Int =
-                Settings.Global.getInt(context.contentResolver,
-                        Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-
-        fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
-                Settings.Global.putInt(context.contentResolver,
-                        Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
-
-        fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
-            executeShellCommand(
-                    instrumentation,
-                    createConfigSupportsNonResizableMultiWindowCommand(configValue))
-
-        fun resetMultiWindowConfig(instrumentation: Instrumentation) =
-            executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
-
-        private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
-                "wm set-multi-window-config --supportsNonResizable $configValue"
-
-        private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
deleted file mode 100644
index bdc05e7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ /dev/null
@@ -1,223 +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.flicker.helpers
-
-import android.app.Instrumentation
-import android.media.session.MediaController
-import android.media.session.MediaSessionManager
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.traces.common.Rect
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
-import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
-import com.android.wm.shell.flicker.testapp.Components
-
-class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.PipActivity.LABEL,
-    Components.PipActivity.COMPONENT.toFlickerComponent()
-) {
-    private val mediaSessionManager: MediaSessionManager
-        get() = context.getSystemService(MediaSessionManager::class.java)
-            ?: error("Could not get MediaSessionManager")
-
-    private val mediaController: MediaController?
-        get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
-            it.packageName == `package`
-        }
-
-    fun clickObject(resId: String) {
-        val selector = By.res(`package`, resId)
-        val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
-        if (!isTelevision) {
-            obj.click()
-        } else {
-            focusOnObject(selector) || error("Could not focus on `$resId` object")
-            uiDevice.pressDPadCenter()
-        }
-    }
-
-    /**
-     * Launches the app through an intent instead of interacting with the launcher and waits
-     * until the app window is in PIP mode
-     */
-    @JvmOverloads
-    fun launchViaIntentAndWaitForPip(
-        wmHelper: WindowManagerStateHelper,
-        expectedWindowName: String = "",
-        action: String? = null,
-        stringExtras: Map<String, String>
-    ) {
-        launchViaIntentAndWaitShown(
-            wmHelper, expectedWindowName, action, stringExtras,
-            waitConditions = arrayOf(WindowManagerConditionsFactory.hasPipWindow())
-        )
-
-        wmHelper.StateSyncBuilder()
-            .withPipShown()
-            .waitForAndVerify()
-    }
-
-    /**
-     * Expand the PIP window back to full screen via intent and wait until the app is visible
-     */
-    fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
-        launchViaIntentAndWaitShown(wmHelper)
-
-    private fun focusOnObject(selector: BySelector): Boolean {
-        // We expect all the focusable UI elements to be arranged in a way so that it is possible
-        // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
-        // from "the bottom".
-        repeat(FOCUS_ATTEMPTS) {
-            uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
-                ?: error("The object we try to focus on is gone.")
-
-            uiDevice.pressDPadDown()
-            uiDevice.waitForIdle()
-        }
-        return false
-    }
-
-    fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
-        clickObject(ENTER_PIP_BUTTON_ID)
-
-        // Wait on WMHelper or simply wait for 3 seconds
-        wmHelper.StateSyncBuilder()
-            .withPipShown()
-            .waitForAndVerify()
-        // when entering pip, the dismiss button is visible at the start. to ensure the pip
-        // animation is complete, wait until the pip dismiss button is no longer visible.
-        // b/176822698: dismiss-only state will be removed in the future
-        uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
-    }
-
-    fun enableEnterPipOnUserLeaveHint() {
-        clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
-    }
-
-    fun enableAutoEnterForPipActivity() {
-        clickObject(ENTER_PIP_AUTOENTER)
-    }
-
-    fun clickStartMediaSessionButton() {
-        clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
-    }
-
-    fun checkWithCustomActionsCheckbox() = uiDevice
-        .findObject(By.res(`package`, WITH_CUSTOM_ACTIONS_BUTTON_ID))
-        ?.takeIf { it.isCheckable }
-        ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
-        ?: error("'With custom actions' checkbox not found")
-
-    fun pauseMedia() = mediaController?.transportControls?.pause()
-        ?: error("No active media session found")
-
-    fun stopMedia() = mediaController?.transportControls?.stop()
-        ?: error("No active media session found")
-
-    @Deprecated(
-        "Use PipAppHelper.closePipWindow(wmHelper) instead",
-        ReplaceWith("closePipWindow(wmHelper)")
-    )
-    fun closePipWindow() {
-        if (isTelevision) {
-            uiDevice.closeTvPipWindow()
-        } else {
-            closePipWindow(WindowManagerStateHelper(mInstrumentation))
-        }
-    }
-
-    private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
-        val windowRegion = wmHelper.getWindowRegion(this)
-        require(!windowRegion.isEmpty) {
-            "Unable to find a PIP window in the current state"
-        }
-        return windowRegion.bounds
-    }
-
-    /**
-     * Taps the pip window and dismisses it by clicking on the X button.
-     */
-    fun closePipWindow(wmHelper: WindowManagerStateHelper) {
-        if (isTelevision) {
-            uiDevice.closeTvPipWindow()
-        } else {
-            val windowRect = getWindowRect(wmHelper)
-            uiDevice.click(windowRect.centerX(), windowRect.centerY())
-            // search and interact with the dismiss button
-            val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-            uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-            val dismissPipObject = uiDevice.findObject(dismissSelector)
-                ?: error("PIP window dismiss button not found")
-            val dismissButtonBounds = dismissPipObject.visibleBounds
-            uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-        }
-
-        // Wait for animation to complete.
-        wmHelper.StateSyncBuilder()
-            .withPipGone()
-            .withHomeActivityVisible()
-            .waitForAndVerify()
-    }
-
-    /**
-     * Close the pip window by pressing the expand button
-     */
-    fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the expand button
-        val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
-        uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
-        val expandPipObject = uiDevice.findObject(expandSelector)
-            ?: error("PIP window expand button not found")
-        val expandButtonBounds = expandPipObject.visibleBounds
-        uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
-        wmHelper.StateSyncBuilder()
-            .withPipGone()
-            .withFullScreenApp(this)
-            .waitForAndVerify()
-    }
-
-    /**
-     * Double click on the PIP window to expand it
-     */
-    fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        wmHelper.StateSyncBuilder()
-            .withAppTransitionIdle()
-            .waitForAndVerify()
-    }
-
-    companion object {
-        private const val FOCUS_ATTEMPTS = 20
-        private const val ENTER_PIP_BUTTON_ID = "enter_pip"
-        private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
-        private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
-        private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
-        private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
deleted file mode 100644
index 52e5d7e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ /dev/null
@@ -1,354 +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.flicker.helpers
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-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.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.traces.common.IComponentMatcher
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
-
-class SplitScreenHelper(
-    instrumentation: Instrumentation,
-    activityLabel: String,
-    componentInfo: IComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
-
-    companion object {
-        const val TIMEOUT_MS = 3_000L
-        const val DRAG_DURATION_MS = 1_000L
-        const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-        const val DIVIDER_BAR = "docked_divider_handle"
-        const val GESTURE_STEP_MS = 16L
-        const val LONG_PRESS_TIME_MS = 100L
-
-        private val notificationScrollerSelector: BySelector
-            get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-        private val notificationContentSelector: BySelector
-            get() = By.text("Notification content")
-        private val dividerBarSelector: BySelector
-            get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-
-        fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SplitScreenActivity.LABEL,
-                Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SplitScreenSecondaryActivity.LABEL,
-                Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.NonResizeableActivity.LABEL,
-                Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SendNotificationActivity.LABEL,
-                Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.ImeActivity.LABEL,
-                Components.ImeActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun waitForSplitComplete(
-            wmHelper: WindowManagerStateHelper,
-            primaryApp: IComponentMatcher,
-            secondaryApp: IComponentMatcher,
-        ) {
-            wmHelper.StateSyncBuilder()
-                .withWindowSurfaceAppeared(primaryApp)
-                .withWindowSurfaceAppeared(secondaryApp)
-                .withSplitDividerVisible()
-                .waitForAndVerify()
-        }
-
-        fun enterSplit(
-            wmHelper: WindowManagerStateHelper,
-            tapl: LauncherInstrumentation,
-            primaryApp: SplitScreenHelper,
-            secondaryApp: SplitScreenHelper
-        ) {
-            tapl.workspace.switchToOverview().dismissAllTasks()
-            primaryApp.launchViaIntent(wmHelper)
-            secondaryApp.launchViaIntent(wmHelper)
-            tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
-            splitFromOverview(tapl)
-            waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-        }
-
-        fun splitFromOverview(tapl: LauncherInstrumentation) {
-            // 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.workspace.switchToOverview().overviewActions
-                    .clickSplit()
-                    .currentTask
-                    .open()
-            } 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, 20 /* steps */
-            )
-            SystemClock.sleep(TIMEOUT_MS)
-
-            // Find the target notification
-            val notificationScroller = device.wait(
-                Until.findObject(notificationScrollerSelector), TIMEOUT_MS
-            )
-            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 longPress(
-            instrumentation: Instrumentation,
-            point: Point
-        ) {
-            val downTime = SystemClock.uptimeMillis()
-            touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
-            SystemClock.sleep(LONG_PRESS_TIME_MS)
-            touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
-        }
-
-        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))
-
-            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)
-            longPress(instrumentation, textView.getVisibleCenter())
-
-            val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-            copyBtn.click()
-
-            // Paste text to destinationApp
-            val editText = device.wait(Until.findObject(
-                By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
-            longPress(instrumentation, editText.getVisibleCenter())
-
-            val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-            pasteBtn.click()
-
-            // Verify text
-            if (!textView.getText().contentEquals(editText.getText())) {
-                error("Fail to copy content in split")
-            }
-        }
-    }
-}
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 9684bb3..7d498dc 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
@@ -22,7 +22,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
@@ -41,27 +40,27 @@
  * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
  *
  * Actions:
+ * ```
  *     Launch an app in full screen
  *     Select "Auto-enter PiP" radio button
  *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
- *
+ * ```
  * Notes:
+ * ```
  *     1. All assertions are inherited from [EnterPipTest]
  *     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)
 @FlakyTest(bugId = 238367575)
-@Group3
 class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
@@ -78,9 +77,7 @@
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
-            transitions {
-                tapl.goHome()
-            }
+            transitions { tapl.goHome() }
         }
 
     @FlakyTest
@@ -94,10 +91,8 @@
         }
     }
 
-    /**
-     * Checks that [pipApp] window is animated towards default position in right bottom corner
-     */
-    @Presubmit
+    /** Checks that [pipApp] window is animated towards default position in right bottom corner */
+    @FlakyTest(bugId = 251135384)
     @Test
     fun pipLayerMovesTowardsRightBottomCorner() {
         // in gestural nav the swipe makes PiP first go upwards
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 59f7ecf..c8aa6d2 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
@@ -16,15 +16,12 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
@@ -41,26 +38,26 @@
  * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
  *
  * Actions:
+ * ```
  *     Launch an app in full screen
  *     Select "Via code behind" radio button
  *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
- *
+ * ```
  * Notes:
+ * ```
  *     1. All assertions are inherited from [EnterPipTest]
  *     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)
-@Group3
 class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
@@ -75,20 +72,17 @@
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
-            transitions {
-                tapl.goHome()
-            }
+            transitions { tapl.goHome() }
         }
 
     @Presubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
-        if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else {
+        if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
+        else {
             // pip layer in gesture nav will disappear during transition
             testSpec.assertLayers {
-                this.isVisible(pipApp)
-                    .then().isInvisible(pipApp)
-                    .then().isVisible(pipApp)
+                this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
             }
         }
     }
@@ -112,28 +106,17 @@
     @Presubmit
     @Test
     override fun entireScreenCovered() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        super.entireScreenCovered()
-    }
-
-    @FlakyTest(bugId = 227313015)
-    @Test
-    fun entireScreenCovered_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
         super.entireScreenCovered()
     }
 
     @Presubmit
     @Test
     override fun pipLayerRemainInsideVisibleBounds() {
-        if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else {
+        if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
+        else {
             // pip layer in gesture nav will disappear during transition
-            testSpec.assertLayersStart {
-                this.visibleRegion(pipApp).coversAtMost(displayBounds)
-            }
-            testSpec.assertLayersEnd {
-                this.visibleRegion(pipApp).coversAtMost(displayBounds)
-            }
+            testSpec.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+            testSpec.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index c9e38e4..2b629e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,14 +16,12 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
@@ -41,25 +39,27 @@
  * To run this test: `atest WMShellFlickerTests:EnterPipTest`
  *
  * Actions:
+ * ```
  *     Launch an app in full screen
  *     Press an "enter pip" button to put [pipApp] in pip mode
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited from [PipTransition]
  *     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)
-@Group3
 open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
@@ -72,31 +72,23 @@
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
-            transitions {
-                pipApp.clickEnterPipButton(wmHelper)
-            }
+            transitions { pipApp.clickEnterPipButton(wmHelper) }
         }
 
-    /**
-     * Checks [pipApp] window remains visible throughout the animation
-     */
+    /** Checks [pipApp] window remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipAppWindowAlwaysVisible() {
-        testSpec.assertWm {
-            this.isAppWindowVisible(pipApp)
-        }
+        testSpec.assertWm { this.isAppWindowVisible(pipApp) }
     }
 
     /**
      * Checks [pipApp] layer remains visible throughout the animation
      */
-    @FlakyTest(bugId = 239807171)
+    @Presubmit
     @Test
     open fun pipAppLayerAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(pipApp)
-        }
+        testSpec.assertLayers { this.isVisible(pipApp) }
     }
 
     /**
@@ -106,26 +98,20 @@
     @Presubmit
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
      * Checks that the pip app layer remains inside the display bounds throughout the whole
      * animation
      */
-    @FlakyTest(bugId = 239807171)
+    @Presubmit
     @Test
     open fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
-    /**
-     * Checks that the visible region of [pipApp] always reduces during the animation
-     */
+    /** Checks that the visible region of [pipApp] always reduces during the animation */
     @Presubmit
     @Test
     open fun pipLayerReduces() {
@@ -137,9 +123,7 @@
         }
     }
 
-    /**
-     * Checks that [pipApp] window becomes pinned
-     */
+    /** Checks that [pipApp] window becomes pinned */
     @Presubmit
     @Test
     fun pipWindowBecomesPinned() {
@@ -150,9 +134,7 @@
         }
     }
 
-    /**
-     * Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation
-     */
+    /** Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation */
     @Presubmit
     @Test
     fun launcherLayerBecomesVisible() {
@@ -164,31 +146,27 @@
     }
 
     /**
-     * Checks that the focus changes between the [pipApp] window and the launcher when
-     * closing the pip window
+     * Checks that the focus changes between the [pipApp] window and the launcher when closing the
+     * pip window
      */
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog {
-            this.focusChanges(pipApp.`package`, "NexusLauncherActivity")
-        }
+        testSpec.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 87d800c..b4594de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -18,26 +18,25 @@
 
 import android.app.Activity
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -68,11 +67,10 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
 class EnterPipToOtherOrientationTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
-    private val testApp = FixedAppHelper(instrumentation)
+    private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
 
@@ -127,7 +125,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] has the correct position at
+     * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
      * the start and end of the transition
      */
     @FlakyTest
@@ -143,6 +141,12 @@
     @Test
     fun entireScreenCoveredAtStartAndEnd() = testSpec.entireScreenCovered(allStates = false)
 
+    @FlakyTest(bugId = 251219769)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
     /**
      * Checks [pipApp] window remains visible and on top throughout the transition
      */
@@ -226,7 +230,7 @@
     }
 
     /** {@inheritDoc}  */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
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 45851c8..3d8525b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,14 +18,12 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import org.junit.Test
 
-/**
- * Base class for pip expand tests
- */
+/** Base class for pip expand tests */
 abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    protected val testApp = FixedAppHelper(instrumentation)
+    protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /**
      * Checks that the pip app window remains inside the display bounds throughout the whole
@@ -34,9 +32,7 @@
     @Presubmit
     @Test
     open fun pipAppWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -46,9 +42,7 @@
     @Presubmit
     @Test
     open fun pipAppLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -78,44 +72,34 @@
     @Test
     open fun showBothAppLayersThenHidePip() {
         testSpec.assertLayers {
-            isVisible(testApp)
-                .isVisible(pipApp)
-                .then()
-                .isInvisible(testApp)
-                .isVisible(pipApp)
+            isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
         }
     }
 
     /**
-     * Checks that the visible region of [testApp] plus the visible region of [pipApp]
-     * cover the full display area at the start of the transition
+     * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+     * full display area at the start of the transition
      */
     @Presubmit
     @Test
     open fun testPlusPipAppsCoverFullScreenAtStart() {
         testSpec.assertLayersStart {
             val pipRegion = visibleRegion(pipApp).region
-            visibleRegion(testApp)
-                .plus(pipRegion)
-                .coversExactly(displayBounds)
+            visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
         }
     }
 
     /**
-     * Checks that the visible region oft [pipApp] covers the full display area at the end of
-     * the transition
+     * Checks that the visible region oft [pipApp] covers the full display area at the end of the
+     * transition
      */
     @Presubmit
     @Test
     open fun pipAppCoversFullScreenAtEnd() {
-        testSpec.assertLayersEnd {
-            visibleRegion(pipApp).coversExactly(displayBounds)
-        }
+        testSpec.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
     }
 
-    /**
-     * Checks that the visible region of [pipApp] always expands during the animation
-     */
+    /** Checks that the visible region of [pipApp] always expands during the animation */
     @Presubmit
     @Test
     open fun pipLayerExpands() {
@@ -127,8 +111,6 @@
         }
     }
 
-    /** {@inheritDoc}  */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 39be89d..3b8bb90 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -25,24 +25,18 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
 import org.junit.Test
 
-/**
- * Base class for exiting pip (closing pip window) without returning to the app
- */
+/** Base class for exiting pip (closing pip window) without returning to the app */
 abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
-            setup {
-                this.setRotation(testSpec.startRotation)
-            }
-            teardown {
-                this.setRotation(Surface.ROTATION_0)
-            }
+            setup { this.setRotation(testSpec.startRotation) }
+            teardown { this.setRotation(Surface.ROTATION_0) }
         }
 
     /**
-     * Checks that [pipApp] window is pinned and visible at the start and then becomes
-     * unpinned and invisible at the same moment, and remains unpinned and invisible
-     * until the end of the transition
+     * Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and
+     * invisible at the same moment, and remains unpinned and invisible until the end of the
+     * transition
      */
     @Presubmit
     @Test
@@ -53,30 +47,24 @@
             // and isAppWindowInvisible in the same assertion block.
             testSpec.assertWm {
                 this.invoke("hasPipWindow") {
-                    it.isPinned(pipApp)
-                        .isAppWindowVisible(pipApp)
-                        .isAppWindowOnTop(pipApp)
-                }.then().invoke("!hasPipWindow") {
-                    it.isNotPinned(pipApp)
-                        .isAppWindowNotOnTop(pipApp)
-                }
+                        it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
+                    }
+                    .then()
+                    .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
             }
             testSpec.assertWmEnd { isAppWindowInvisible(pipApp) }
         } else {
             testSpec.assertWm {
-                this.invoke("hasPipWindow") {
-                    it.isPinned(pipApp).isAppWindowVisible(pipApp)
-                }.then().invoke("!hasPipWindow") {
-                    it.isNotPinned(pipApp).isAppWindowInvisible(pipApp)
-                }
+                this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
+                    .then()
+                    .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
             }
         }
     }
 
     /**
-     * Checks that [pipApp] and [LAUNCHER] layers are visible at the start
-     * of the transition. Then [pipApp] layer becomes invisible, and remains invisible
-     * until the end of the transition
+     * Checks that [pipApp] and [LAUNCHER] layers are visible at the start of the transition. Then
+     * [pipApp] layer becomes invisible, and remains invisible until the end of the transition
      */
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index c2fd0d7..6bf7e8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -53,7 +53,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
 class ExitPipViaExpandButtonClickTest(
     testSpec: FlickerTestParameter
 ) : ExitPipToAppTransition(testSpec) {
@@ -78,7 +77,7 @@
         }
 
     /** {@inheritDoc}  */
-    @FlakyTest(bugId = 227313015)
+    @Presubmit
     @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 0d75e02..3356d3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
@@ -39,28 +38,28 @@
  * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
  *
  * Actions:
+ * ```
  *     Launch an app in pip mode [pipApp],
  *     Launch another full screen mode [testApp]
  *     Expand [pipApp] app to full screen via an intent
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited from [PipTransition]
  *     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)
-@Group3
 class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
 
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
@@ -71,18 +70,16 @@
                 // This will bring PipApp to fullscreen
                 pipApp.exitPipToFullScreenViaIntent(wmHelper)
                 // Wait until the other app is no longer visible
-                wmHelper.StateSyncBuilder()
-                    .withWindowSurfaceDisappeared(testApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
             }
         }
 
     /** {@inheritDoc}  */
-    @FlakyTest
+    @Presubmit
     @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun statusBarLayerPositionAtStartAndEnd() {
@@ -97,7 +94,7 @@
         super.statusBarLayerPositionAtStartAndEnd()
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @FlakyTest(bugId = 197726610)
     @Test
     override fun pipLayerExpands() {
@@ -116,14 +113,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0))
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 3bffef0..d195abb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -36,31 +35,31 @@
  * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
  *
  * Actions:
+ * ```
  *     Launch an app in pip mode [pipApp],
  *     Click on the pip window
  *     Click on dismiss button and wait window disappear
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     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)
-@Group3
 class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            transitions {
-                pipApp.closePipWindow(wmHelper)
-            }
+            transitions { pipApp.closePipWindow(wmHelper) }
         }
 
     /**
@@ -70,23 +69,21 @@
     @Presubmit
     @Test
     fun focusChanges() {
-        testSpec.assertEventLog {
-            this.focusChanges("PipMenuView", "NexusLauncherActivity")
-        }
+        testSpec.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 75d25e6..f7a2447 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
@@ -37,22 +36,24 @@
  * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
  *
  * Actions:
+ * ```
  *     Launch an app in pip mode [pipApp],
  *     Swipe the pip window to the bottom-center of the screen and wait it disappear
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     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)
-@Group3
 class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -62,20 +63,24 @@
                 val pipCenterX = pipRegion.centerX()
                 val pipCenterY = pipRegion.centerY()
                 val displayCenterX = device.displayWidth / 2
-                val barComponent = if (testSpec.isTablet) {
-                    ComponentNameMatcher.TASK_BAR
-                } else {
-                    ComponentNameMatcher.NAV_BAR
-                }
-                val barLayerHeight = wmHelper.currentState.layerState
-                    .getLayerWithBuffer(barComponent)
-                    ?.visibleRegion
-                    ?.height ?: error("Couldn't find Nav or Task bar layer")
+                val barComponent =
+                    if (testSpec.isTablet) {
+                        ComponentNameMatcher.TASK_BAR
+                    } else {
+                        ComponentNameMatcher.NAV_BAR
+                    }
+                val barLayerHeight =
+                    wmHelper.currentState.layerState
+                        .getLayerWithBuffer(barComponent)
+                        ?.visibleRegion
+                        ?.height
+                        ?: error("Couldn't find Nav or Task bar layer")
                 // The dismiss button doesn't appear at the complete bottom of the screen,
                 val displayY = device.displayHeight - barLayerHeight
                 device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
                 // Wait until the other app is no longer visible
-                wmHelper.StateSyncBuilder()
+                wmHelper
+                    .StateSyncBuilder()
                     .withPipGone()
                     .withWindowSurfaceDisappeared(pipApp)
                     .withAppTransitionIdle()
@@ -83,29 +88,25 @@
             }
         }
 
-    /**
-     * Checks that the focus doesn't change between windows during the transition
-     */
+    /** Checks that the focus doesn't change between windows during the transition */
     @Presubmit
     @Test
     fun focusDoesNotChange() {
-        testSpec.assertEventLog {
-            this.focusDoesNotChange()
-        }
+        testSpec.assertEventLog { this.focusDoesNotChange() }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.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 513ce95..fa5ce5b 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
@@ -17,13 +17,11 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
@@ -53,7 +51,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
 class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
@@ -66,7 +63,7 @@
      * Checks that the pip app window remains inside the display bounds throughout the whole
      * animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
         testSpec.assertWmVisibleRegion(pipApp) {
@@ -78,7 +75,7 @@
      * Checks that the pip app layer remains inside the display bounds throughout the whole
      * animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerRemainInsideVisibleBounds() {
         testSpec.assertLayersVisibleRegion(pipApp) {
@@ -89,7 +86,7 @@
     /**
      * Checks [pipApp] window remains visible throughout the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipWindowIsAlwaysVisible() {
         testSpec.assertWm {
@@ -100,7 +97,7 @@
     /**
      * Checks [pipApp] layer remains visible throughout the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerIsAlwaysVisible() {
         testSpec.assertLayers {
@@ -111,7 +108,7 @@
     /**
      * Checks that the visible region of [pipApp] always expands during the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerExpands() {
         testSpec.assertLayers {
@@ -122,7 +119,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun pipSameAspectRatio() {
         testSpec.assertLayers {
@@ -136,7 +133,7 @@
     /**
      * Checks [pipApp] window remains pinned throughout the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun windowIsAlwaysPinned() {
         testSpec.assertWm {
@@ -147,7 +144,7 @@
     /**
      * Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 249308003)
     @Test
     fun launcherIsAlwaysVisible() {
         testSpec.assertLayers {
@@ -166,6 +163,72 @@
         }
     }
 
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 216306753)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
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
new file mode 100644
index 0000000..bcd01a4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.platform.test.annotations.Postsubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+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)
+class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+    override val transition: FlickerBuilder.() -> Unit
+        get() = buildTransition {
+            transitions {
+                pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30)
+            }
+        }
+
+    /**
+     * Checks that the visible region area of [pipApp] always increases during the animation.
+     */
+    @Postsubmit
+    @Test
+    fun pipLayerAreaIncreases() {
+        testSpec.assertLayers {
+            val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+            pipLayerList.zipWithNext { previous, current ->
+                previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+            }
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+         * repetitions, screen orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(
+                    supportedRotations = listOf(Surface.ROTATION_0)
+                )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 746ce91..0c0228e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
@@ -54,7 +53,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
 class MovePipDownShelfHeightChangeTest(
     testSpec: FlickerTestParameter
 ) : MovePipShelfHeightTransition(testSpec) {
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 19bdca5..b401067 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
@@ -18,39 +18,28 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.wm.shell.flicker.Direction
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Test
 
-/**
- * Base class for pip tests with Launcher shelf height change
- */
-abstract class MovePipShelfHeightTransition(
-    testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
-    protected val testApp = FixedAppHelper(instrumentation)
+/** Base class for pip tests with Launcher shelf height change */
+abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
+    PipTransition(testSpec) {
+    protected val testApp = FixedOrientationAppHelper(instrumentation)
 
-    /**
-     * Checks [pipApp] window remains visible throughout the animation
-     */
+    /** Checks [pipApp] window remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipWindowIsAlwaysVisible() {
-        testSpec.assertWm {
-            isAppWindowVisible(pipApp)
-        }
+        testSpec.assertWm { isAppWindowVisible(pipApp) }
     }
 
-    /**
-     * Checks [pipApp] layer remains visible throughout the animation
-     */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipLayerIsAlwaysVisible() {
-        testSpec.assertLayers {
-            isVisible(pipApp)
-        }
+        testSpec.assertLayers { isVisible(pipApp) }
     }
 
     /**
@@ -60,9 +49,7 @@
     @Presubmit
     @Test
     open fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -72,9 +59,7 @@
     @Presubmit
     @Test
     open fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -83,9 +68,8 @@
      */
     protected fun pipWindowMoves(direction: Direction) {
         testSpec.assertWm {
-            val pipWindowFrameList = this.windowStates {
-                pipApp.windowMatchesAnyOf(it) && it.isVisible
-            }.map { it.frame }
+            val pipWindowFrameList =
+                this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame }
             when (direction) {
                 Direction.UP -> assertRegionMovementUp(pipWindowFrameList)
                 Direction.DOWN -> assertRegionMovementDown(pipWindowFrameList)
@@ -100,9 +84,9 @@
      */
     protected fun pipLayerMoves(direction: Direction) {
         testSpec.assertLayers {
-            val pipLayerRegionList = this.layers {
-                pipApp.layerMatchesAnyOf(it) && it.isVisible
-            }.map { it.visibleRegion }
+            val pipLayerRegionList =
+                this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+                    .map { it.visibleRegion }
             when (direction) {
                 Direction.UP -> assertRegionMovementUp(pipLayerRegionList)
                 Direction.DOWN -> assertRegionMovementDown(pipLayerRegionList)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 93e7d5c..7f8ef32 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
@@ -54,7 +53,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
 open class MovePipUpShelfHeightChangeTest(
     testSpec: FlickerTestParameter
 ) : MovePipShelfHeightTransition(testSpec) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3d3b53d..3b64d21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -22,13 +22,12 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.ComponentNameMatcher
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -37,15 +36,11 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
- */
+/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
 open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
     private val imeApp = ImeAppHelper(instrumentation)
 
@@ -54,7 +49,7 @@
         assumeFalse(isShellTransitionsEnabled)
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
@@ -75,9 +70,7 @@
             }
         }
 
-    /**
-     * Ensure the pip window remains visible throughout any keyboard interactions
-     */
+    /** Ensure the pip window remains visible throughout any keyboard interactions */
     @Presubmit
     @Test
     open fun pipInVisibleBounds() {
@@ -87,15 +80,11 @@
         }
     }
 
-    /**
-     * Ensure that the pip window does not obscure the keyboard
-     */
+    /** Ensure that the pip window does not obscure the keyboard */
     @Presubmit
     @Test
     open fun pipIsAboveAppWindow() {
-        testSpec.assertWmTag(TAG_IME_VISIBLE) {
-            isAboveWindow(ComponentNameMatcher.IME, pipApp)
-        }
+        testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 3e00b19..2a82c00 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
 import org.junit.Before
@@ -34,7 +33,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
 class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
 
     @Before
@@ -44,6 +42,5 @@
 
     @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index f13698f..7de5494 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -23,12 +23,11 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -43,26 +42,28 @@
  * To run this test: `atest WMShellFlickerTests:PipRotationTest`
  *
  * Actions:
+ * ```
  *     Launch a [pipApp] in pip mode
  *     Launch another app [fixedApp] (appears below pip)
  *     Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
  *     (usually, 0->90 and 90->0)
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited from [PipTransition]
  *     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)
-@Group4
 open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    private val fixedApp = FixedAppHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
     private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
@@ -74,121 +75,93 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
-                fixedApp.launchViaIntent(wmHelper)
+                testApp.launchViaIntent(wmHelper)
                 setRotation(testSpec.startRotation)
             }
-            transitions {
-                setRotation(testSpec.endRotation)
-            }
+            transitions { setRotation(testSpec.endRotation) }
         }
 
-    /**
-     * Checks the position of the navigation bar at the start and end of the transition
-     */
+    /** Checks the position of the navigation bar at the start and end of the transition */
     @FlakyTest(bugId = 240499181)
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
-    /**
-     * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
-     */
+    /** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */
     @Presubmit
     @Test
     fun fixedAppLayer_StartingBounds() {
-        testSpec.assertLayersStart {
-            visibleRegion(fixedApp).coversAtMost(screenBoundsStart)
-        }
+        testSpec.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
     }
 
-    /**
-     * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
-     */
+    /** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */
     @Presubmit
     @Test
     fun fixedAppLayer_EndingBounds() {
-        testSpec.assertLayersEnd {
-            visibleRegion(fixedApp).coversAtMost(screenBoundsEnd)
-        }
+        testSpec.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
     }
 
     /**
-     * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
-     * of the transition
+     * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start of the
+     * transition
      */
     @Presubmit
     @Test
     fun appLayers_StartingBounds() {
         testSpec.assertLayersStart {
-            visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsStart)
+            visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
         }
     }
 
     /**
-     * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
-     * of the transition
+     * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end of the
+     * transition
      */
     @Presubmit
     @Test
     fun appLayers_EndingBounds() {
         testSpec.assertLayersEnd {
-            visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsEnd)
+            visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
         }
     }
 
-    /**
-     * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
-     */
+    /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
     private fun pipLayerRotates_StartingBounds_internal() {
-        testSpec.assertLayersStart {
-            visibleRegion(pipApp).coversAtMost(screenBoundsStart)
-        }
+        testSpec.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
     }
 
-    /**
-     * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
-     */
+    /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
     @Presubmit
     @Test
     fun pipLayerRotates_StartingBounds() {
         pipLayerRotates_StartingBounds_internal()
     }
 
-    /**
-     * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
-     */
+    /** Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition */
     @Presubmit
     @Test
     fun pipLayerRotates_EndingBounds() {
-        testSpec.assertLayersEnd {
-            visibleRegion(pipApp).coversAtMost(screenBoundsEnd)
-        }
+        testSpec.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
     }
 
     /**
-     * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
-     * transition
+     * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the transition
      */
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_Start() {
-        testSpec.assertWmStart {
-            isAboveWindow(pipApp, fixedApp)
-        }
+        testSpec.assertWmStart { isAboveWindow(pipApp, testApp) }
     }
 
     /**
-     * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
-     * transition
+     * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the transition
      */
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_End() {
-        testSpec.assertWmEnd {
-            isAboveWindow(pipApp, fixedApp)
-        }
+        testSpec.assertWmEnd { isAboveWindow(pipApp, testApp) }
     }
 
-    @FlakyTest(bugId = 240499181)
+    @Presubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() {
         super.navBarLayerIsVisibleAtStartAndEnd()
@@ -198,15 +171,16 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            )
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigRotationTests(
+                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
+                )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
index 737f16a..983cb1c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
 import org.junit.Before
@@ -36,24 +35,26 @@
  * To run this test: `atest WMShellFlickerTests:PipRotationTest_ShellTransit`
  *
  * Actions:
+ * ```
  *     Launch a [pipApp] in pip mode
  *     Launch another app [fixedApp] (appears below pip)
  *     Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
  *     (usually, 0->90 and 90->0)
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited from [PipTransition]
  *     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)
-@Group4
 @FlakyTest(bugId = 239575053)
 class PipRotationTest_ShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
     @Before
@@ -61,9 +62,8 @@
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @FlakyTest
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 }
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 b67cf77..dfa2510 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
@@ -21,12 +21,12 @@
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.testapp.Components
 
 abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected val pipApp = PipAppHelper(instrumentation)
@@ -40,32 +40,29 @@
         }
 
         fun doAction(broadcastAction: String) {
-            instrumentation.context
-                .sendBroadcast(createIntentWithAction(broadcastAction))
+            instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction))
         }
 
         companion object {
             // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-            @JvmStatic
-            val ORIENTATION_LANDSCAPE = 0
+            @JvmStatic val ORIENTATION_LANDSCAPE = 0
 
             // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-            @JvmStatic
-            val ORIENTATION_PORTRAIT = 1
+            @JvmStatic val ORIENTATION_PORTRAIT = 1
         }
     }
 
     /**
-     * Gets a configuration that handles basic setup and teardown of pip tests and that
-     * launches the Pip app for test
+     * Gets a configuration that handles basic setup and teardown of pip tests and that launches the
+     * Pip app for test
      *
      * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
      * @param stringExtras Arguments to pass to the PIP launch intent
-     * @param extraSpec Addicional segment of flicker specification
+     * @param extraSpec Additional segment of flicker specification
      */
     @JvmOverloads
     protected open fun buildTransition(
-        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+        stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
         extraSpec: FlickerBuilder.() -> Unit = {}
     ): FlickerBuilder.() -> Unit {
         return {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 866e4e8..f0093e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -25,15 +25,15 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.testapp.Components
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -50,7 +50,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
 open class SetRequestedOrientationWhilePinnedTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
@@ -68,7 +67,7 @@
                 pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
                     EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
                 // Enter PiP.
-                broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
+                broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
                 // System bar may fade out during fixed rotation.
                 wmHelper.StateSyncBuilder()
                     .withPipShown()
@@ -144,9 +143,7 @@
         }
     }
 
-    @Presubmit
-    @Test
-    fun pipLayerInsideDisplay() {
+    private fun pipLayerInsideDisplay_internal() {
         testSpec.assertLayersStart {
             visibleRegion(pipApp).coversAtMost(startingBounds)
         }
@@ -154,6 +151,20 @@
 
     @Presubmit
     @Test
+    fun pipLayerInsideDisplay() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        pipLayerInsideDisplay_internal()
+    }
+
+    @FlakyTest(bugId = 250527829)
+    @Test
+    fun pipLayerInsideDisplay_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        pipLayerInsideDisplay_internal()
+    }
+
+    @Presubmit
+    @Test
     fun pipAlwaysVisible() {
         testSpec.assertWm {
             this.isAppWindowVisible(pipApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
new file mode 100644
index 0000000..36909dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.flicker.pip.tv
+
+import android.app.Instrumentation
+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.PipAppHelper
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+/** Helper class for PIP app on AndroidTV */
+open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
+    private val appSelector = By.pkg(`package`).depth(0)
+
+    val ui: UiObject2?
+        get() = uiDevice.findObject(appSelector)
+
+    private fun focusOnObject(selector: BySelector): Boolean {
+        // We expect all the focusable UI elements to be arranged in a way so that it is possible
+        // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
+        // from "the bottom".
+        repeat(FOCUS_ATTEMPTS) {
+            uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
+                ?: error("The object we try to focus on is gone.")
+
+            uiDevice.pressDPadDown()
+            uiDevice.waitForIdle()
+        }
+        return false
+    }
+
+    override fun clickObject(resId: String) {
+        val selector = By.res(`package`, resId)
+        focusOnObject(selector) || error("Could not focus on `$resId` object")
+        uiDevice.pressDPadCenter()
+    }
+
+    @Deprecated(
+        "Use PipAppHelper.closePipWindow(wmHelper) instead",
+        ReplaceWith("closePipWindow(wmHelper)")
+    )
+    override fun closePipWindow() {
+        uiDevice.closeTvPipWindow()
+    }
+
+    /** Taps the pip window and dismisses it by clicking on the X button. */
+    override fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+        uiDevice.closeTvPipWindow()
+
+        // Wait for animation to complete.
+        wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    fun waitUntilClosed(): Boolean {
+        val appSelector = By.pkg(`package`).depth(0)
+        return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
+    }
+
+    companion object {
+        private const val FOCUS_ATTEMPTS = 20
+        private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 180ced0..2cb18f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -21,14 +21,10 @@
 import android.view.Surface
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import org.junit.Before
 import org.junit.runners.Parameterized
 
-abstract class PipTestBase(
-    protected val rotationName: String,
-    protected val rotation: Int
-) {
+abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
     val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     val uiDevice = UiDevice.getInstance(instrumentation)
     val packageManager: PackageManager = instrumentation.context.packageManager
@@ -38,7 +34,7 @@
                 hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
         }
     }
-    protected val testApp = PipAppHelper(instrumentation)
+    protected val testApp = PipAppHelperTv(instrumentation)
 
     @Before
     open fun televisionSetUp() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 31fb16f..8a073ab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
@@ -25,16 +25,11 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
-/**
- * Test Pip Menu on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipBasicTest`
- */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */
 @RequiresDevice
 @RunWith(Parameterized::class)
-class TvPipBasicTest(
-    private val radioButtonId: String,
-    private val pipWindowRatio: Rational?
-) : TvPipTestBase() {
+class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) :
+    TvPipTestBase() {
 
     @Test
     fun enterPip_openMenu_pressBack_closePip() {
@@ -45,8 +40,8 @@
         testApp.clickObject(radioButtonId)
         testApp.clickEnterPipButton(wmHelper)
 
-        val actualRatio: Float = testApp.ui?.visibleBounds?.ratio
-                ?: fail("Application UI not found")
+        val actualRatio: Float =
+            testApp.ui?.visibleBounds?.ratio ?: fail("Application UI not found")
         pipWindowRatio?.let { expectedRatio ->
             assertEquals("Wrong Pip window ratio", expectedRatio.toFloat(), actualRatio)
         }
@@ -62,7 +57,8 @@
         // Make sure Pip Window ration remained the same after Pip menu was closed
         testApp.ui?.visibleBounds?.let { newBounds ->
             assertEquals("Pip window ratio has changed", actualRatio, newBounds.ratio)
-        } ?: fail("Application UI not found")
+        }
+            ?: fail("Application UI not found")
 
         // Close Pip
         testApp.closePipWindow()
@@ -77,10 +73,10 @@
         fun getParams(): Collection<Array<Any?>> {
             infix fun Int.to(denominator: Int) = Rational(this, denominator)
             return listOf(
-                    arrayOf("ratio_default", null),
-                    arrayOf("ratio_square", 1 to 1),
-                    arrayOf("ratio_wide", 2 to 1),
-                    arrayOf("ratio_tall", 1 to 2)
+                arrayOf("ratio_default", null),
+                arrayOf("ratio_square", 1 to 1),
+                arrayOf("ratio_wide", 2 to 1),
+                arrayOf("ratio_tall", 1 to 2)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 4be19d6..7403aab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -19,36 +19,34 @@
 import android.graphics.Rect
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.UiObject2
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
 import com.android.wm.shell.flicker.wait
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 
-/**
- * Test Pip Menu on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipMenuTests`
- */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */
 @RequiresDevice
 class TvPipMenuTests : TvPipTestBase() {
 
     private val systemUiResources =
-            packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
-    private val pipBoundsWhileInMenu: Rect = systemUiResources.run {
-        val bounds = getString(getIdentifier("pip_menu_bounds", "string",
-                SYSTEM_UI_PACKAGE_NAME))
-        Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds")
-    }
-    private val playButtonDescription = systemUiResources.run {
-        getString(getIdentifier("pip_play", "string",
-                SYSTEM_UI_PACKAGE_NAME))
-    }
-    private val pauseButtonDescription = systemUiResources.run {
-        getString(getIdentifier("pip_pause", "string",
-                SYSTEM_UI_PACKAGE_NAME))
-    }
+        packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+    private val pipBoundsWhileInMenu: Rect =
+        systemUiResources.run {
+            val bounds =
+                getString(getIdentifier("pip_menu_bounds", "string", SYSTEM_UI_PACKAGE_NAME))
+            Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds")
+        }
+    private val playButtonDescription =
+        systemUiResources.run {
+            getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME))
+        }
+    private val pauseButtonDescription =
+        systemUiResources.run {
+            getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME))
+        }
 
     @Before
     fun tvPipMenuTestsTestUp() {
@@ -61,20 +59,29 @@
         enterPip_openMenu_assertShown()
 
         // Make sure the PiP task is positioned where it should be.
-        val activityBounds: Rect = testApp.ui?.visibleBounds
-                ?: error("Could not retrieve Pip Activity bounds")
-        assertTrue("Pip Activity is positioned correctly while Pip menu is shown",
-                pipBoundsWhileInMenu == activityBounds)
+        val activityBounds: Rect =
+            testApp.ui?.visibleBounds ?: error("Could not retrieve Pip Activity bounds")
+        assertTrue(
+            "Pip Activity is positioned correctly while Pip menu is shown",
+            pipBoundsWhileInMenu == activityBounds
+        )
 
         // Make sure the Pip Menu Actions are positioned correctly.
         uiDevice.findTvPipMenuControls()?.visibleBounds?.run {
-            assertTrue("Pip Menu Actions should be positioned below the Activity in Pip",
-                top >= activityBounds.bottom)
-            assertTrue("Pip Menu Actions should be positioned central horizontally",
-                centerX() == uiDevice.displayWidth / 2)
-            assertTrue("Pip Menu Actions should be fully shown on the screen",
-                left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight)
-        } ?: error("Could not retrieve Pip Menu Actions bounds")
+            assertTrue(
+                "Pip Menu Actions should be positioned below the Activity in Pip",
+                top >= activityBounds.bottom
+            )
+            assertTrue(
+                "Pip Menu Actions should be positioned central horizontally",
+                centerX() == uiDevice.displayWidth / 2
+            )
+            assertTrue(
+                "Pip Menu Actions should be fully shown on the screen",
+                left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight
+            )
+        }
+            ?: error("Could not retrieve Pip Menu Actions bounds")
 
         testApp.closePipWindow()
     }
@@ -107,7 +114,7 @@
 
         // PiP menu should contain the Close button
         uiDevice.findTvPipMenuCloseButton()
-                ?: fail("\"Close PIP\" button should be shown in Pip menu")
+            ?: fail("\"Close PIP\" button should be shown in Pip menu")
 
         // Clicking on the Close button should close the app
         uiDevice.clickTvPipMenuCloseButton()
@@ -120,13 +127,15 @@
 
         // PiP menu should contain the Fullscreen button
         uiDevice.findTvPipMenuFullscreenButton()
-                ?: fail("\"Full screen\" button should be shown in Pip menu")
+            ?: fail("\"Full screen\" button should be shown in Pip menu")
 
         // Clicking on the fullscreen button should return app to the fullscreen mode.
         // Click, wait for the app to go fullscreen
         uiDevice.clickTvPipMenuFullscreenButton()
-        assertTrue("\"Full screen\" button should open the app fullscreen",
-                wait { testApp.ui?.isFullscreen(uiDevice) ?: false })
+        assertTrue(
+            "\"Full screen\" button should open the app fullscreen",
+            wait { testApp.ui?.isFullscreen(uiDevice) ?: false }
+        )
 
         // Close the app
         uiDevice.pressBack()
@@ -143,8 +152,10 @@
 
         // PiP menu should contain the Pause button
         uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
-                ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
-                        "playing media session.")
+            ?: fail(
+                "\"Pause\" button should be shown in Pip menu if there is an active " +
+                    "playing media session."
+            )
 
         // When we pause media, the button should change from Pause to Play
         uiDevice.clickTvPipMenuElementWithDescription(pauseButtonDescription)
@@ -152,8 +163,10 @@
         assertFullscreenAndCloseButtonsAreShown()
         // PiP menu should contain the Play button now
         uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription)
-                ?: fail("\"Play\" button should be shown in Pip menu if there is an active " +
-                        "paused media session.")
+            ?: fail(
+                "\"Play\" button should be shown in Pip menu if there is an active " +
+                    "paused media session."
+            )
 
         testApp.closePipWindow()
     }
@@ -165,44 +178,47 @@
         enterPip_openMenu_assertShown()
 
         // PiP menu should contain "No-Op", "Off" and "Clear" buttons...
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
-                ?: fail("\"No-Op\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
-                ?: fail("\"Off\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
-                ?: fail("\"Clear\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+            ?: fail("\"No-Op\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+            ?: fail("\"Off\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+            ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... and should also contain the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
         // Invoking the "Off" action should replace it with the "On" action/button and should
         // remove the "No-Op" action/button. "Clear" action/button should remain in the menu ...
-        uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON)
-                ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
-        assertNull("\"No-Op\" button should not be shown in Pip menu",
-                uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_NO_OP))
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
-                        ?: fail("\"Clear\" button should be shown in Pip menu")
+        uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON)
+            ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
+        assertNull(
+            "\"No-Op\" button should not be shown in Pip menu",
+            uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+        )
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+            ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... as well as the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
         // Invoking the "Clear" action should remove all the custom actions and their corresponding
         // buttons, ...
-        uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(
-            Components.PipActivity.MENU_ACTION_ON)?.also {
-            isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu")
-        }
-        assertNull("\"Off\" button should not be shown in Pip menu",
-                uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_OFF))
-        assertNull("\"Clear\" button should not be shown in Pip menu",
-                uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_CLEAR))
-        assertNull("\"No-Op\" button should not be shown in Pip menu",
-                uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_NO_OP))
+        uiDevice
+            .waitUntilTvPipMenuElementWithDescriptionIsGone(ActivityOptions.Pip.MENU_ACTION_ON)
+            ?.also { isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu") }
+        assertNull(
+            "\"Off\" button should not be shown in Pip menu",
+            uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+        )
+        assertNull(
+            "\"Clear\" button should not be shown in Pip menu",
+            uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+        )
+        assertNull(
+            "\"No-Op\" button should not be shown in Pip menu",
+            uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+        )
         // ... but the menu should still contain the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
@@ -217,26 +233,32 @@
         enterPip_openMenu_assertShown()
 
         // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
-                ?: fail("\"No-Op\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
-                ?: fail("\"Off\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
-                ?: fail("\"Clear\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+            ?: fail("\"No-Op\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+            ?: fail("\"Off\" button should be shown in Pip menu")
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+            ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... should also contain the "Full screen" and "Close" buttons, ...
         assertFullscreenAndCloseButtonsAreShown()
         // ... but should not contain media buttons.
-        assertNull("\"Play\" button should not be shown in menu when there are custom actions",
-                uiDevice.findTvPipMenuElementWithDescription(playButtonDescription))
-        assertNull("\"Pause\" button should not be shown in menu when there are custom actions",
-                uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription))
+        assertNull(
+            "\"Play\" button should not be shown in menu when there are custom actions",
+            uiDevice.findTvPipMenuElementWithDescription(playButtonDescription)
+        )
+        assertNull(
+            "\"Pause\" button should not be shown in menu when there are custom actions",
+            uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
+        )
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
         // Invoking the "Clear" action should remove all the custom actions, which should bring up
         // media buttons...
         uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription)
-                ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
-                        "playing media session.")
+            ?: fail(
+                "\"Pause\" button should be shown in Pip menu if there is an active " +
+                    "playing media session."
+            )
         // ... while the "Full screen" and "Close" buttons should remain in the menu.
         assertFullscreenAndCloseButtonsAreShown()
 
@@ -252,8 +274,8 @@
 
     private fun assertFullscreenAndCloseButtonsAreShown() {
         uiDevice.findTvPipMenuCloseButton()
-                ?: fail("\"Close PIP\" button should be shown in Pip menu")
+            ?: fail("\"Close PIP\" button should be shown in Pip menu")
         uiDevice.findTvPipMenuFullscreenButton()
-                ?: fail("\"Full screen\" button should be shown in Pip menu")
+            ?: fail("\"Full screen\" button should be shown in Pip menu")
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 134e97b..90406c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -34,8 +34,8 @@
 import org.junit.Test
 
 /**
- * Test Pip Notifications on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipNotificationTests`
+ * Test Pip Notifications on TV. To run this test: `atest
+ * WMShellFlickerTests:TvPipNotificationTests`
  */
 @RequiresDevice
 class TvPipNotificationTests : TvPipTestBase() {
@@ -58,13 +58,17 @@
         testApp.launchViaIntent()
         testApp.clickEnterPipButton(wmHelper)
 
-        assertNotNull("Pip notification should have been posted",
-                waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) })
+        assertNotNull(
+            "Pip notification should have been posted",
+            waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+        )
 
         testApp.closePipWindow()
 
-        assertTrue("Pip notification should have been dismissed",
-                waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) })
+        assertTrue(
+            "Pip notification should have been dismissed",
+            waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }
+        )
     }
 
     @Test
@@ -72,17 +76,20 @@
         testApp.launchViaIntent()
         testApp.clickEnterPipButton(wmHelper)
 
-        val notification: StatusBarNotification = waitForNotificationToAppear {
-            it.isPipNotificationWithTitle(testApp.appName)
-        } ?: fail("Pip notification should have been posted")
+        val notification: StatusBarNotification =
+            waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+                ?: fail("Pip notification should have been posted")
 
-        notification.deleteIntent?.send()
-            ?: fail("Pip notification should contain `delete_intent`")
+        notification.deleteIntent?.send() ?: fail("Pip notification should contain `delete_intent`")
 
-        assertTrue("Pip should have closed by sending the `delete_intent`",
-                testApp.waitUntilClosed())
-        assertTrue("Pip notification should have been dismissed",
-                waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) })
+        assertTrue(
+            "Pip should have closed by sending the `delete_intent`",
+            testApp.waitUntilClosed()
+        )
+        assertTrue(
+            "Pip notification should have been dismissed",
+            waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }
+        )
     }
 
     @Test
@@ -90,15 +97,17 @@
         testApp.launchViaIntent(wmHelper)
         testApp.clickEnterPipButton(wmHelper)
 
-        val notification: StatusBarNotification = waitForNotificationToAppear {
-            it.isPipNotificationWithTitle(testApp.appName)
-        } ?: fail("Pip notification should have been posted")
+        val notification: StatusBarNotification =
+            waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+                ?: fail("Pip notification should have been posted")
 
         notification.contentIntent?.send()
             ?: fail("Pip notification should contain `content_intent`")
 
-        assertNotNull("Pip menu should have been shown after sending `content_intent`",
-                uiDevice.waitForTvPipMenu())
+        assertNotNull(
+            "Pip menu should have been shown after sending `content_intent`",
+            uiDevice.waitForTvPipMenu()
+        )
 
         uiDevice.pressBack()
         testApp.closePipWindow()
@@ -112,35 +121,38 @@
         testApp.clickEnterPipButton(wmHelper)
 
         // Wait for the correct notification to show up...
-        waitForNotificationToAppear {
-            it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING)
-        } ?: fail("Pip notification with media session title should have been posted")
+        waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) }
+            ?: fail("Pip notification with media session title should have been posted")
         // ... and make sure "regular" PiP notification is now shown
-        assertNull("Regular notification should not have been posted",
-            findNotification { it.isPipNotificationWithTitle(testApp.appName) })
+        assertNull(
+            "Regular notification should not have been posted",
+            findNotification { it.isPipNotificationWithTitle(testApp.appName) }
+        )
 
         // Pause the media session. When paused the application updates the title for the media
         // session. This change should be reflected in the notification.
         testApp.pauseMedia()
 
         // Wait for the "paused" notification to show up...
-        waitForNotificationToAppear {
-            it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED)
-        } ?: fail("Pip notification with media session title should have been posted")
+        waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) }
+            ?: fail("Pip notification with media session title should have been posted")
         // ... and make sure "playing" PiP notification is gone
-        assertNull("Regular notification should not have been posted",
-                findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) })
+        assertNull(
+            "Regular notification should not have been posted",
+            findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) }
+        )
 
         // Now stop the media session, which should revert the title to the "default" one.
         testApp.stopMedia()
 
         // Wait for the "regular" notification to show up...
-        waitForNotificationToAppear {
-            it.isPipNotificationWithTitle(testApp.appName)
-        } ?: fail("Pip notification with media session title should have been posted")
+        waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+            ?: fail("Pip notification with media session title should have been posted")
         // ... and make sure previous ("paused") notification is gone
-        assertNull("Regular notification should not have been posted",
-                findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) })
+        assertNull(
+            "Regular notification should not have been posted",
+            findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) }
+        )
 
         testApp.closePipWindow()
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index aeff0ac..dc1fe47 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -68,7 +68,8 @@
         fun start() {
             hasDied = false
             uiAutomation.adoptShellPermissionIdentity(
-                    android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                android.Manifest.permission.SET_ACTIVITY_WATCHER
+            )
             activityManager.registerProcessObserver(this)
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index 1c66340..b0adbe1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -33,32 +33,31 @@
 private const val FOCUS_ATTEMPTS = 10
 private const val WAIT_TIME_MS = 3_000L
 
-private val TV_PIP_MENU_SELECTOR =
-        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)
+private val TV_PIP_MENU_SELECTOR = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)
 private val TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR =
-        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID)
+    By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID)
 private val TV_PIP_MENU_CLOSE_BUTTON_SELECTOR =
-        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID)
+    By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID)
 private val TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR =
-        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID)
+    By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID)
 
 fun UiDevice.waitForTvPipMenu(): UiObject2? =
-        wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
+    wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
 
 fun UiDevice.waitForTvPipMenuToClose(): Boolean =
-        wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
+    wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
 
 fun UiDevice.findTvPipMenuControls(): UiObject2? =
-        findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR)
+    findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR)
 
 fun UiDevice.findTvPipMenuCloseButton(): UiObject2? =
-        findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
+    findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
 
 fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? =
-        findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)
+    findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)
 
 fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? =
-        findTvPipMenuElement(By.desc(desc))
+    findTvPipMenuElement(By.desc(desc))
 
 private fun UiDevice.findTvPipMenuElement(selector: BySelector): UiObject2? =
     findObject(TV_PIP_MENU_SELECTOR)?.findObject(selector)
@@ -70,11 +69,10 @@
     // descendant and then retrieve the element from the menu and return to the caller of this
     // method.
     val elementSelector = By.desc(desc)
-    val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR)
-            .hasDescendant(elementSelector)
+    val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector)
 
     return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS)
-            ?.findObject(elementSelector)
+        ?.findObject(elementSelector)
 }
 
 fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? {
@@ -86,18 +84,17 @@
 
 fun UiDevice.clickTvPipMenuCloseButton() {
     focusOnAndClickTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) ||
-            error("Could not focus on the Close button")
+        error("Could not focus on the Close button")
 }
 
 fun UiDevice.clickTvPipMenuFullscreenButton() {
     focusOnAndClickTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) ||
-            error("Could not focus on the Fullscreen button")
+        error("Could not focus on the Fullscreen button")
 }
 
 fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) {
-    focusOnAndClickTvPipMenuElement(By.desc(desc)
-            .pkg(SYSTEM_UI_PACKAGE_NAME)) ||
-            error("Could not focus on the Pip menu object with \"$desc\" description")
+    focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) ||
+        error("Could not focus on the Pip menu object with \"$desc\" description")
     // So apparently Accessibility framework on TV is not very reliable and sometimes the state of
     // the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of
     // the "real" state of the "UI tree". It seems, however, that moving focus around the tree
@@ -110,7 +107,8 @@
 
 private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Boolean {
     repeat(FOCUS_ATTEMPTS) {
-        val element = findTvPipMenuElement(selector)
+        val element =
+            findTvPipMenuElement(selector)
                 ?: error("The Pip Menu element we try to focus on is gone.")
 
         if (element.isFocusedOrHasFocusedChild) {
@@ -119,10 +117,11 @@
         }
 
         findTvPipMenuElement(By.focused(true))?.let { focused ->
-            if (element.visibleCenter.x < focused.visibleCenter.x)
-                pressDPadLeft() else pressDPadRight()
+            if (element.visibleCenter.x < focused.visibleCenter.x) pressDPadLeft()
+            else pressDPadRight()
             waitForIdle()
-        } ?: error("Pip menu does not contain a focused element")
+        }
+            ?: error("Pip menu does not contain a focused element")
     }
 
     return false
@@ -155,9 +154,8 @@
 
 fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW)
 
-fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
-    height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
-}
+fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean =
+    visibleBounds.run { height() == uiDevice.displayHeight && width() == uiDevice.displayWidth }
 
 val UiObject2.isFocusedOrHasFocusedChild: Boolean
     get() = isFocused || findObject(By.focused(true)) != null
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 3c439fd..9b1247a 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,20 +17,21 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.IwTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 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.helpers.SplitScreenHelper
 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 org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,121 +47,123 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-    private val textEditApp = SplitScreenHelper.getIme(instrumentation)
+    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
-            }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp) }
             transitions {
-                SplitScreenHelper.copyContentInSplit(
-                    instrumentation, device, primaryApp, textEditApp)
+                SplitScreenUtils.copyContentInSplit(
+                    instrumentation,
+                    device,
+                    primaryApp,
+                    textEditApp
+                )
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+    @Presubmit @Test fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsKeepVisible() =
+        testSpec.splitAppLayerBoundsKeepVisible(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
-        textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    fun textEditAppBoundsKeepVisible() =
+        testSpec.splitAppLayerBoundsKeepVisible(
+            textEditApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+    @Presubmit @Test fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -169,10 +172,12 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(
+                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                )
         }
     }
 }
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 60e5f78..ec8bc45 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
@@ -24,17 +25,18 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,21 +52,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
                 if (tapl.isTablet) {
-                    SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
                         dragToRight = false, dragToBottom = true)
                 } else {
-                    SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
                         dragToRight = true, dragToBottom = true)
                 }
                 wmHelper.StateSyncBuilder()
@@ -76,28 +77,26 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun secondaryAppBoundsIsFullscreenAtEnd() {
+    private fun secondaryAppBoundsIsFullscreenAtEnd_internal() {
         testSpec.assertLayers {
             this.isVisible(secondaryApp)
                 .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -117,12 +116,24 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsFullscreenAtEnd() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        secondaryAppBoundsIsFullscreenAtEnd_internal()
+    }
+
+    @FlakyTest(bugId = 250528485)
+    @Test
+    fun secondaryAppBoundsIsFullscreenAtEnd_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        secondaryAppBoundsIsFullscreenAtEnd_internal()
+    }
+
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
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 2db3009..a2eefec 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
@@ -18,19 +18,17 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -47,7 +45,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class DismissSplitScreenByGoHome(
     testSpec: FlickerTestParameter
 ) : SplitScreenBase(testSpec) {
@@ -56,7 +53,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
                 tapl.goHome()
@@ -65,14 +62,16 @@
                     .waitForAndVerify()
             }
         }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241525302)
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
@@ -85,86 +84,89 @@
     @FlakyTest(bugId = 245472831)
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+        primaryApp,
+        landscapePosLeft = tapl.isTablet,
+        portraitPosTop = false
+    )
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 250530241)
     @Test
     fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+        secondaryApp,
+        landscapePosLeft = !tapl.isTablet,
+        portraitPosTop = true
+    )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 251268711)
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() =
         super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerPositionAtStartAndEnd() =
         super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarWindowIsAlwaysVisible() =
         super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerPositionAtStartAndEnd() =
         super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarWindowIsAlwaysVisible() =
         super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() =
         super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarWindowIsAlwaysVisible() =
         super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -176,7 +178,8 @@
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+                listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+            )
         }
     }
 }
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 fddd84c..1cf0a97 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
@@ -25,13 +26,15 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 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.helpers.SplitScreenHelper
 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 org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,31 +50,43 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
-                SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+                SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerVisibilityChanges() {
@@ -84,24 +99,20 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
         primaryApp, landscapePosLeft = true, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 250530664)
     @Test
     fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
         secondaryApp, landscapePosLeft = false, portraitPosTop = true)
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 a7c6898..7378e21 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
@@ -24,18 +24,17 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -54,7 +53,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class EnterSplitScreenByDragFromAllApps(
     testSpec: FlickerTestParameter
 ) : SplitScreenBase(testSpec) {
@@ -76,20 +74,23 @@
                         .openAllApps()
                         .getAppIcon(secondaryApp.appName)
                         .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -99,53 +100,28 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.assertLayers {
-            this.isInvisible(secondaryApp)
-                .then()
-                .isVisible(secondaryApp)
-                .then()
-                .isInvisible(secondaryApp)
-                .then()
-                .isVisible(secondaryApp)
-        }
-    }
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    // TODO(b/245472831): Align to legacy transition after shell transition ready.
-    @Presubmit
-    @Test
-    fun secondaryAppLayerBecomesVisible_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.layerBecomesVisible(secondaryApp)
-    }
-
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
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 7d8a8db..0c03d31 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
@@ -21,22 +21,19 @@
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -55,12 +52,11 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class EnterSplitScreenByDragFromNotification(
     testSpec: FlickerTestParameter
 ) : SplitScreenBase(testSpec) {
 
-    private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
 
     @Before
     fun before() {
@@ -74,18 +70,13 @@
             setup {
                 // Send a notification
                 sendNotificationApp.launchViaIntent(wmHelper)
-                val sendNotification = device.wait(
-                    Until.findObject(By.text("Send Notification")),
-                    SplitScreenHelper.TIMEOUT_MS
-                )
-                sendNotification?.click() ?: error("Send notification button not found")
-
+                sendNotificationApp.postNotification(wmHelper)
                 tapl.goHome()
                 primaryApp.launchViaIntent(wmHelper)
             }
             transitions {
-                SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
             }
             teardown {
                 sendNotificationApp.exit(wmHelper)
@@ -95,13 +86,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -111,12 +105,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -140,24 +132,20 @@
         testSpec.layerBecomesVisible(sendNotificationApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         sendNotificationApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
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 bfd8a3a..496d439 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
@@ -24,18 +24,17 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -54,7 +53,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class EnterSplitScreenByDragFromTaskbar(
     testSpec: FlickerTestParameter
 ) : SplitScreenBase(testSpec) {
@@ -70,7 +68,7 @@
             super.transition(this)
             setup {
                 tapl.goHome()
-                SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(
                     tapl, secondaryApp.appName
                 )
                 primaryApp.launchViaIntent(wmHelper)
@@ -79,20 +77,23 @@
                 tapl.launchedAppState.taskbar
                     .getAppIcon(secondaryApp.appName)
                     .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -102,12 +103,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -131,24 +130,20 @@
         testSpec.layerBecomesVisible(secondaryApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
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 cefb9f5..201594b 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
@@ -23,15 +24,17 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,9 +50,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -64,56 +65,65 @@
                     .waitForAndVerify()
             }
             transitions {
-                SplitScreenHelper.splitFromOverview(tapl)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.splitFromOverview(tapl)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    fun secondaryAppBoundsBecomesVisible() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.splitAppLayerBoundsBecomesVisible(
+            secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    }
 
-    @IwTest(focusArea = "sysui")
+    @FlakyTest(bugId = 244407465)
+    @Test
+    fun secondaryAppBoundsBecomesVisible_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        testSpec.splitAppLayerBoundsBecomesVisible(
+            secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    }
+
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 251269324)
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() =
         super.navBarLayerIsVisibleAtStartAndEnd()
@@ -125,49 +135,49 @@
         super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarWindowIsAlwaysVisible() =
         super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerPositionAtStartAndEnd() =
         super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarWindowIsAlwaysVisible() =
         super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() =
         super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarWindowIsAlwaysVisible() =
         super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
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 eab473c..e6d6379 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
@@ -21,12 +21,11 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 
 abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected val context: Context = instrumentation.context
-    protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
-    protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+    protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
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
new file mode 100644
index 0000000..6453ed8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.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.helpers.StandardAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+
+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 GESTURE_STEP_MS = 16L
+    private const val LONG_PRESS_TIME_MS = 100L
+    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)
+
+    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,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        tapl.workspace.switchToOverview().dismissAllTasks()
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation) {
+        // 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.workspace.switchToOverview().overviewActions.clickSplit().currentTask.open()
+        } 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 longPress(instrumentation: Instrumentation, point: Point) {
+        val downTime = SystemClock.uptimeMillis()
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+        SystemClock.sleep(LONG_PRESS_TIME_MS)
+        touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+    }
+
+    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))
+
+        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
+            )
+        longPress(instrumentation, textView.visibleCenter)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        longPress(instrumentation, editText.visibleCenter)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        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 2d5d2b7..813ac5d 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
@@ -25,14 +25,17 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isRotated
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,58 +51,115 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
-                SplitScreenHelper.doubleTapDividerToSwitch(device)
+                SplitScreenUtils.doubleTapDividerToSwitch(device)
                 wmHelper.StateSyncBuilder()
                     .withAppTransitionIdle()
                     .waitForAndVerify()
+
+                waitForLayersToSwitch(wmHelper)
+                waitForWindowsToSwitch(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 (testSpec.startRotation.isRotated()) {
+                return@add primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+            } else {
+                return@add 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 (testSpec.startRotation.isRotated()) {
+                return@add primaryVisibleRegion.right <= secondaryVisibleRegion.left
+            } else {
+                return@add primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+            }
+        }.waitForAndVerify()
+    }
+
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @FlakyTest(bugId = 241524174)
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241524174)
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241524174)
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241524174)
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+        primaryApp,
+        landscapePosLeft = !tapl.isTablet,
+        portraitPosTop = true
+    )
 
     // TODO(b/246490534): Move back to presubmit after withAppTransitionIdle is robust enough to
     // get the correct end state.
     @FlakyTest(bugId = 246490534)
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+        secondaryApp,
+        landscapePosLeft = tapl.isTablet,
+        portraitPosTop = false
+    )
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241524174)
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
+    @FlakyTest(bugId = 241524174)
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
 
@@ -176,7 +236,8 @@
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+                listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+            )
         }
     }
 }
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 20c6af7..553840c 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
@@ -16,21 +16,20 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,126 +45,120 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-    val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+    val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 thirdApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder()
-                    .withWindowSurfaceAppeared(thirdApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
             }
             transitions {
                 tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -174,10 +167,12 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(
+                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                )
         }
     }
 }
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 cb9ca9f..e2f7f7e 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
@@ -16,21 +16,20 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,125 +45,119 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
             transitions {
                 tapl.workspace.quickSwitchToPreviousApp()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -173,10 +166,12 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(
+                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                )
         }
     }
 }
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 2662767..d7b3ec2 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
@@ -16,21 +16,20 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,127 +45,119 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
             transitions {
-                tapl.workspace.switchToOverview()
-                    .currentTask
-                    .open()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                tapl.workspace.switchToOverview().currentTask.open()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -175,10 +166,12 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+            return FlickerTestParameterFactory.getInstance()
+                .getConfigNonRotationTests(
+                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
deleted file mode 100644
index ea606df..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
+++ /dev/null
@@ -1,35 +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 {
-    // 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: "WMShellFlickerTestApp",
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    test_suites: ["device-tests"],
-}
-
-java_library {
-    name: "wmshell-flicker-test-components",
-    srcs: ["src/**/Components.java"],
-    sdk_version: "test_current",
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
deleted file mode 100644
index bc0b0b6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ /dev/null
@@ -1,147 +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.testapp">
-
-    <uses-sdk android:minSdkVersion="29"
-         android:targetSdkVersion="29"/>
-    <application android:allowBackup="false"
-         android:supportsRtl="true">
-        <activity android:name=".FixedActivity"
-                  android:resizeableActivity="true"
-                  android:supportsPictureInPicture="true"
-                  android:launchMode="singleTop"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="FixedApp"
-                  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=".PipActivity"
-                 android:resizeableActivity="true"
-                 android:supportsPictureInPicture="true"
-                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
-                 android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
-                 android:theme="@style/CutoutShortEdges"
-                 android:launchMode="singleTop"
-                 android:label="PipApp"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".ImeActivity"
-                 android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
-                 android:theme="@style/CutoutShortEdges"
-                 android:label="ImeApp"
-                 android:launchMode="singleTop"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".SplitScreenActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SplitScreenPrimaryApp"
-                  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=".SplitScreenSecondaryActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SplitScreenSecondaryApp"
-                  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=".SendNotificationActivity"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SendNotificationApp"
-                  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=".NonResizeableActivity"
-                  android:resizeableActivity="false"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="NonResizeableApp"
-                  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=".SimpleActivity"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SimpleApp"
-                  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=".LaunchBubbleActivity"
-            android:label="LaunchBubbleApp"
-            android:exported="true"
-            android:theme="@style/CutoutShortEdges"
-            android:launchMode="singleTop">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".BubbleActivity"
-            android:label="BubbleApp"
-            android:exported="false"
-            android:theme="@style/CutoutShortEdges"
-            android:resizeableActivity="true" />
-    </application>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
deleted file mode 100644
index b43f31d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
deleted file mode 100644
index 0e8c7a0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
--->
-<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="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
-      android:fillColor="#000000"
-      android:fillType="evenOdd"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
deleted file mode 100644
index 4708cfd..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
+++ /dev/null
@@ -1,27 +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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusableInTouchMode="true"
-    android:background="@android:color/holo_green_light">
-    <EditText android:id="@+id/plain_text_input"
-              android:layout_height="wrap_content"
-              android:layout_width="match_parent"
-              android:inputType="text"/>
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
deleted file mode 100644
index 45d5917..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_orange_light">
-
-    <TextView
-        android:id="@+id/NonResizeableTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:text="NonResizeableActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
deleted file mode 100644
index 8d59b56..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.
--->
-<RelativeLayout 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_send_notification"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerHorizontal="true"
-            android:layout_centerVertical="true"
-            android:text="Send Notification" />
-</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
deleted file mode 100644
index 5d94e51..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
+++ /dev/null
@@ -1,23 +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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@android:color/holo_orange_light">
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
deleted file mode 100644
index 642a08b5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_green_light">
-
-    <TextView
-        android:id="@+id/SplitScreenTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:textIsSelectable="true"
-        android:text="PrimaryActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
deleted file mode 100644
index 674bb70..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_blue_light">
-
-    <TextView
-        android:id="@+id/SplitScreenTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:text="SecondaryActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
deleted file mode 100644
index 23b51cc..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
+++ /dev/null
@@ -1,34 +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="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
-        <item name="android:windowBackground">@android:color/darker_gray</item>
-    </style>
-
-    <style name="CutoutDefault" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">default</item>
-    </style>
-
-    <style name="CutoutShortEdges" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-
-    <style name="CutoutNever" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">never</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
deleted file mode 100644
index a2b580d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ /dev/null
@@ -1,108 +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.flicker.testapp;
-
-import android.content.ComponentName;
-
-public class Components {
-    public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
-
-    public static class SimpleActivity {
-        public static final String LABEL = "SimpleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SimpleActivity");
-    }
-
-    public static class FixedActivity {
-        public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-        public static final String LABEL = "FixedApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".FixedActivity");
-    }
-
-    public static class NonResizeableActivity {
-        public static final String LABEL = "NonResizeableApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".NonResizeableActivity");
-    }
-
-    public static class PipActivity {
-        // Test App > Pip Activity
-        public static final String LABEL = "PipApp";
-        public static final String MENU_ACTION_NO_OP = "No-Op";
-        public static final String MENU_ACTION_ON = "On";
-        public static final String MENU_ACTION_OFF = "Off";
-        public static final String MENU_ACTION_CLEAR = "Clear";
-
-        // Intent action that this activity dynamically registers to enter picture-in-picture
-        public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
-        // Intent action that this activity dynamically registers to set requested orientation.
-        // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
-        public static final String ACTION_SET_REQUESTED_ORIENTATION =
-                PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
-
-        // Calls enterPictureInPicture() on creation
-        public static final String EXTRA_ENTER_PIP = "enter_pip";
-        // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
-        public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
-        // Adds a click listener to finish this activity when it is clicked
-        public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".PipActivity");
-    }
-
-    public static class ImeActivity {
-        public static final String LABEL = "ImeApp";
-        public static final String ACTION_CLOSE_IME =
-                PACKAGE_NAME + ".action.CLOSE_IME";
-        public static final String ACTION_OPEN_IME =
-                PACKAGE_NAME + ".action.OPEN_IME";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".ImeActivity");
-    }
-
-    public static class SplitScreenActivity {
-        public static final String LABEL = "SplitScreenPrimaryApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SplitScreenActivity");
-    }
-
-    public static class SplitScreenSecondaryActivity {
-        public static final String LABEL = "SplitScreenSecondaryApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SplitScreenSecondaryActivity");
-    }
-
-    public static class SendNotificationActivity {
-        public static final String LABEL = "SendNotificationApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SendNotificationActivity");
-    }
-
-    public static class LaunchBubbleActivity {
-        public static final String LABEL = "LaunchBubbleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".LaunchBubbleActivity");
-    }
-
-    public static class BubbleActivity {
-        public static final String LABEL = "BubbleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".BubbleActivity");
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
deleted file mode 100644
index 59c64a1..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
+++ /dev/null
@@ -1,66 +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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-
-public class ImeActivity extends Activity {
-    private static final String ACTION_OPEN_IME =
-            "com.android.wm.shell.flicker.testapp.action.OPEN_IME";
-    private static final String ACTION_CLOSE_IME =
-            "com.android.wm.shell.flicker.testapp.action.CLOSE_IME";
-
-    private InputMethodManager mImm;
-    private View mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        WindowManager.LayoutParams p = getWindow().getAttributes();
-        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
-                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        getWindow().setAttributes(p);
-        setContentView(R.layout.activity_ime);
-
-        mEditText = findViewById(R.id.plain_text_input);
-        mImm = getSystemService(InputMethodManager.class);
-
-        handleIntent(getIntent());
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        handleIntent(intent);
-    }
-
-    private void handleIntent(Intent intent) {
-        final String action = intent.getAction();
-        if (ACTION_OPEN_IME.equals(action)) {
-            mEditText.requestFocus();
-            mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
-        } else if (ACTION_CLOSE_IME.equals(action)) {
-            mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
-            mEditText.clearFocus();
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
deleted file mode 100644
index 24275e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
+++ /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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class NonResizeableActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setContentView(R.layout.activity_non_resizeable);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
deleted file mode 100644
index 8020ef2..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
+++ /dev/null
@@ -1,61 +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.testapp;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-
-public class SendNotificationActivity extends Activity {
-    private NotificationManager mNotificationManager;
-    private String mChannelId = "Channel id";
-    private String mChannelName = "Channel name";
-    private NotificationChannel mChannel;
-    private int mNotifyId = 0;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_notification);
-        findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification);
-
-        mChannel = new NotificationChannel(mChannelId, mChannelName,
-                NotificationManager.IMPORTANCE_DEFAULT);
-        mNotificationManager = getSystemService(NotificationManager.class);
-        mNotificationManager.createNotificationChannel(mChannel);
-    }
-
-    private void sendNotification(View v) {
-        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
-                new Intent(this, SendNotificationActivity.class),
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        Notification notification = new Notification.Builder(this, mChannelId)
-                .setContentTitle("Notification App")
-                .setContentText("Notification content")
-                .setWhen(System.currentTimeMillis())
-                .setSmallIcon(R.drawable.ic_message)
-                .setContentIntent(pendingIntent)
-                .build();
-
-        mNotificationManager.notify(mNotifyId, notification);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
deleted file mode 100644
index 5343c18..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
+++ /dev/null
@@ -1,33 +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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class SimpleActivity extends Activity {
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        WindowManager.LayoutParams p = getWindow().getAttributes();
-        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
-                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        getWindow().setAttributes(p);
-        setContentView(R.layout.activity_simple);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index c0720cf..3672ae3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -44,6 +44,7 @@
     private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
     private final Point mPositionInParent = new Point();
     private boolean mIsVisible = false;
+    private long mLastActiveTime;
 
     public static WindowContainerToken createMockWCToken() {
         final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -52,6 +53,11 @@
         return new WindowContainerToken(itoken);
     }
 
+    public TestRunningTaskInfoBuilder setToken(WindowContainerToken token) {
+        mToken = token;
+        return this;
+    }
+
     public TestRunningTaskInfoBuilder setBounds(Rect bounds) {
         mBounds.set(bounds);
         return this;
@@ -95,6 +101,11 @@
         return this;
     }
 
+    public TestRunningTaskInfoBuilder setLastActiveTime(long lastActiveTime) {
+        mLastActiveTime = lastActiveTime;
+        return this;
+    }
+
     public ActivityManager.RunningTaskInfo build() {
         final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
         info.taskId = sNextTaskId++;
@@ -110,6 +121,7 @@
                 mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
         info.positionInParent = mPositionInParent;
         info.isVisible = mIsVisible;
+        info.lastActiveTime = mLastActiveTime;
         return info;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index a7234c1..98b5912 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -18,7 +18,9 @@
 
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -27,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.animation.Animator;
 import android.window.TransitionInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -76,4 +79,18 @@
 
         verify(mController).onAnimationFinished(mTransition);
     }
+
+    @Test
+    public void testChangesBehindStartingWindow() {
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_BEHIND_STARTING_WINDOW);
+        info.addChange(embeddingChange);
+        final Animator animator = mAnimRunner.createAnimator(
+                info, mStartTransaction, mFinishTransaction,
+                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+
+        // The animation should be empty when it is behind starting window.
+        assertEquals(0, animator.getDuration());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 5b3b8fd..6484b07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -18,10 +18,8 @@
 
 import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
@@ -39,7 +37,6 @@
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
@@ -49,11 +46,13 @@
 import android.testing.TestableContentResolver;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
+import android.view.IRemoteAnimationRunner;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.BackEvent;
 import android.window.BackNavigationInfo;
+import android.window.IBackAnimationFinishedCallback;
 import android.window.IOnBackInvokedCallback;
 
 import androidx.test.filters.SmallTest;
@@ -62,10 +61,12 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+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 org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,14 +91,23 @@
             new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
 
     @Mock
-    private SurfaceControl.Transaction mTransaction;
-
-    @Mock
     private IActivityTaskManager mActivityTaskManager;
 
     @Mock
     private IOnBackInvokedCallback mIOnBackInvokedCallback;
 
+    @Mock
+    private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
+
+    @Mock
+    private IRemoteAnimationRunner mBackAnimationRunner;
+
+    @Mock
+    private Transitions mTransitions;
+
+    @Mock
+    private ShellController mShellController;
+
     private BackAnimationController mController;
 
     private int mEventTime = 0;
@@ -114,28 +124,21 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
-        mController = new BackAnimationController(mShellInit,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+        mController = new BackAnimationController(mShellInit, mShellController,
+                mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mTransitions);
         mShellInit.init();
         mEventTime = 0;
         mShellExecutor.flushAll();
     }
 
-    private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
-            SurfaceControl screenshotSurface,
-            HardwareBuffer hardwareBuffer,
-            int backType,
-            IOnBackInvokedCallback onBackInvokedCallback) {
+    private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
         BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
                 .setType(backType)
-                .setDepartingAnimationTarget(topAnimationTarget)
-                .setScreenshotSurface(screenshotSurface)
-                .setScreenshotBuffer(hardwareBuffer)
-                .setTaskWindowConfiguration(new WindowConfiguration())
                 .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
-                .setOnBackInvokedCallback(onBackInvokedCallback);
+                .setOnBackInvokedCallback(onBackInvokedCallback)
+                .setPrepareRemoteAnimation(true);
 
         createNavigationInfo(builder);
     }
@@ -143,7 +146,7 @@
     private void createNavigationInfo(BackNavigationInfo.Builder builder) {
         try {
             doReturn(builder.build()).when(mActivityTaskManager)
-                    .startBackNavigation(anyBoolean(), any());
+                    .startBackNavigation(any(), any());
         } catch (RemoteException ex) {
             ex.rethrowFromSystemServer();
         }
@@ -170,33 +173,9 @@
     }
 
     @Test
-    @Ignore("b/207481538")
-    public void crossActivity_screenshotAttachedAndVisible() {
-        SurfaceControl screenshotSurface = new SurfaceControl();
-        HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
-        createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
-        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-        verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
-        verify(mTransaction).setVisibility(screenshotSurface, true);
-        verify(mTransaction).apply();
-    }
-
-    @Test
-    public void crossActivity_surfaceMovesWithGesture() {
-        SurfaceControl screenshotSurface = new SurfaceControl();
-        HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
-        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
-        // b/207481538, we check that the surface is not moved for now, we can re-enable this once
-        // we implement the animation
-        verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt());
-        verify(mTransaction, never()).setPosition(
-                animationTarget.leash, 100, 100);
-        verify(mTransaction, atLeastOnce()).apply();
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
     }
 
     @Test
@@ -205,7 +184,6 @@
         boolean[] backNavigationDone = new boolean[]{false};
         boolean[] triggerBack = new boolean[]{false};
         createNavigationInfo(new BackNavigationInfo.Builder()
-                .setDepartingAnimationTarget(animationTarget)
                 .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
                 .setOnBackNavigationDone(
                         new RemoteCallback(result -> {
@@ -219,19 +197,19 @@
 
     @Test
     public void backToHome_dispatchesEvents() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
 
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
         ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
-        verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
-        assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
+        verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
@@ -244,18 +222,17 @@
         // Toggle the setting off
         Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
         ShellInit shellInit = new ShellInit(mShellExecutor);
-        mController = new BackAnimationController(shellInit,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+        mController = new BackAnimationController(shellInit, mShellController,
+                mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver);
+                mContentResolver, mTransitions);
         shellInit.init();
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
 
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
         IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
         ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
-        createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
 
         triggerBackGesture();
 
@@ -266,25 +243,31 @@
         verify(mIOnBackInvokedCallback, never()).onBackStarted();
         verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
         verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+        verify(mBackAnimationRunner, never()).onAnimationStart(
+                anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void ignoresGesture_transitionInProgress() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
 
         triggerBackGesture();
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         // Check that back invocation is dispatched.
         verify(mIOnBackInvokedCallback).onBackInvoked();
+        verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
         reset(mIOnBackInvokedCallback);
+        reset(mBackAnimationRunner);
+
         // Verify that we prevent animation from restarting if another gestures happens before
         // the previous transition is finished.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         verifyNoMoreInteractions(mIOnBackInvokedCallback);
-        mController.onBackToLauncherAnimationFinished();
+        mController.onBackAnimationFinished();
+        // Pretend the transition handler called finishAnimation.
+        mController.finishBackNavigation();
 
         // Verify that more events from a rejected swipe cannot start animation.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
@@ -294,39 +277,49 @@
         // Verify that we start accepting gestures again once transition finishes.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void acceptsGesture_transitionTimeout() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
 
         triggerBackGesture();
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
         reset(mIOnBackInvokedCallback);
 
         // Simulate transition timeout.
         mShellExecutor.flushAll();
+        mController.onBackAnimationFinished();
+        // Pretend the transition handler called finishAnimation.
+        mController.finishBackNavigation();
+
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         verify(mIOnBackInvokedCallback).onBackStarted();
     }
 
 
     @Test
     public void cancelBackInvokeWhenLostFocus() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
 
-        createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+        simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         verify(mIOnBackInvokedCallback).onBackStarted();
+        verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
@@ -349,4 +342,14 @@
                 BackEvent.EDGE_LEFT);
         mEventTime += 10;
     }
+
+    private void simulateRemoteAnimationStart(int type) throws RemoteException {
+        RemoteAnimationTarget animationTarget = createAnimationTarget();
+        RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
+        if (mController.mBackAnimationAdapter != null) {
+            mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
+                    targets, null, null, mBackAnimationFinishedCallback);
+            mShellExecutor.flushAll();
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 6292130..2fc0914 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
@@ -93,6 +94,7 @@
     private @Mock Lazy<Transitions> mMockTransitionsLazy;
     private @Mock CompatUIWindowManager mMockCompatLayout;
     private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+    private @Mock DockStateReader mDockStateReader;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -113,7 +115,7 @@
         mShellInit = spy(new ShellInit(mMockExecutor));
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
-                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
+                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index f3a8cf4..16517c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -54,6 +54,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.transition.Transitions;
 
@@ -103,6 +104,7 @@
     @Mock private SurfaceControlViewHost mViewHost;
     @Mock private Transitions mTransitions;
     @Mock private Runnable mOnDismissCallback;
+    @Mock private DockStateReader mDockStateReader;
 
     private SharedPreferences mSharedPreferences;
     @Nullable
@@ -153,6 +155,16 @@
     }
 
     @Test
+    public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
+        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+                true, /* isDocked */ true);
+
+        assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+        assertNull(windowManager.mLayout);
+    }
+
+    @Test
     public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
         LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
                 true, USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -382,17 +394,27 @@
         return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
     }
 
+    private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
+        return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
+                false, isDocked);
+    }
+
     private LetterboxEduWindowManager createWindowManager(boolean eligible,
             int userId, boolean isTaskbarEduShowing) {
+        return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
+    }
+
+    private LetterboxEduWindowManager createWindowManager(boolean eligible,
+            int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+        doReturn(isDocked).when(mDockStateReader).isDocked();
         LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
                 createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
                 createDisplayLayout(), mTransitions, mOnDismissCallback,
-                mAnimationController);
+                mAnimationController, mDockStateReader);
 
         spyOn(windowManager);
         doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
         doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
-
         return windowManager;
     }
 
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 c628f399..c850a3b 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
@@ -19,16 +19,22 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
 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.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.WindowConfiguration;
+import android.app.ActivityManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
@@ -39,13 +45,18 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 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.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,6 +69,8 @@
 public class DesktopModeControllerTest extends ShellTestCase {
 
     @Mock
+    private ShellController mShellController;
+    @Mock
     private ShellTaskOrganizer mShellTaskOrganizer;
     @Mock
     private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -67,18 +80,38 @@
     private Handler mMockHandler;
     @Mock
     private Transitions mMockTransitions;
+    private TestShellExecutor mExecutor;
 
     private DesktopModeController mController;
+    private DesktopModeTaskRepository mDesktopModeTaskRepository;
     private ShellInit mShellInit;
+    private StaticMockitoSession mMockitoSession;
 
     @Before
     public void setUp() {
-        mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+        mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
-        mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
-                mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
+        mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+        mExecutor = new TestShellExecutor();
+
+        mDesktopModeTaskRepository = new DesktopModeTaskRepository();
+
+        mController = new DesktopModeController(mContext, mShellInit, mShellController,
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
+                mDesktopModeTaskRepository, mMockHandler, mExecutor);
+
+        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
+                new WindowContainerTransaction());
 
         mShellInit.init();
+        clearInvocations(mShellTaskOrganizer);
+        clearInvocations(mRootTaskDisplayAreaOrganizer);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
     }
 
     @Test
@@ -159,17 +192,15 @@
         assertThat(wct.getChanges()).hasSize(3);
 
         // Verify executed WCT has a change for setting task windowing mode to undefined
-        Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
-        assertThat(taskWmModeChange).isNotNull();
-        assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+        Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
+        assertThat(taskWmMode).isNotNull();
+        assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
 
         // Verify executed WCT has a change for clearing task bounds
-        Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
-        assertThat(taskBoundsChange).isNotNull();
-        assertThat(taskBoundsChange.getWindowSetMask()
-                & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
-        assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
-                .isTrue();
+        Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
+        assertThat(bounds).isNotNull();
+        assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+        assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
 
         // Verify executed WCT has a change for setting display windowing mode to fullscreen
         Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
@@ -177,6 +208,41 @@
         assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
     }
 
+    @Test
+    public void testShowDesktopApps() {
+        // Set up two active tasks on desktop
+        mDesktopModeTaskRepository.addActiveTask(1);
+        mDesktopModeTaskRepository.addActiveTask(2);
+        MockToken token1 = new MockToken();
+        MockToken token2 = new MockToken();
+        ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
+                token1.token()).setLastActiveTime(100).build();
+        ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
+                token2.token()).setLastActiveTime(200).build();
+        when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
+        when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+
+        // Run show desktop apps logic
+        mController.showDesktopApps();
+        ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+        WindowContainerTransaction wct = wctCaptor.getValue();
+
+        // Check wct has reorder calls
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+
+        // Task 2 has activity later, must be first
+        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(token2.binder());
+
+        // Task 1 should be second
+        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(token2.binder());
+    }
+
     private static class MockToken {
         private final WindowContainerToken mToken;
         private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
new file mode 100644
index 0000000..d378a17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.floating;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
+
+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.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TaskViewTransitions;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.floating.views.FloatingTaskLayer;
+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 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.util.Optional;
+
+/**
+ * Tests for the floating tasks controller.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FloatingTasksControllerTest extends ShellTestCase {
+    // Some behavior in the controller constructor is dependent on this so we can only
+    // validate if it's working for the real value for those things.
+    private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
+
+    @Mock private ShellInit mShellInit;
+    @Mock private ShellController mShellController;
+    @Mock private WindowManager mWindowManager;
+    @Mock private ShellTaskOrganizer mTaskOrganizer;
+    @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor;
+
+    private FloatingTasksController mController;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        WindowMetrics windowMetrics = mock(WindowMetrics.class);
+        WindowInsets windowInsets = mock(WindowInsets.class);
+        Insets insets = Insets.of(0, 0, 0, 0);
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
+        when(windowMetrics.getWindowInsets()).thenReturn(windowInsets);
+        when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000));
+        when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets);
+
+        // For the purposes of this test, just run everything synchronously
+        ShellExecutor shellExecutor = new TestShellExecutor();
+        when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor);
+    }
+
+    @After
+    public void tearDown() {
+        if (mController != null) {
+            mController.removeTask();
+            mController = null;
+        }
+    }
+
+    private void setUpTabletConfig() {
+        Configuration config = mock(Configuration.class);
+        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
+        mController.setConfig(config);
+    }
+
+    private void setUpPhoneConfig() {
+        Configuration config = mock(Configuration.class);
+        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1;
+        mController.setConfig(config);
+    }
+
+    private void createController() {
+        mController = new FloatingTasksController(mContext,
+                mShellInit,
+                mShellController,
+                mock(ShellCommandHandler.class),
+                Optional.empty(),
+                mWindowManager,
+                mTaskOrganizer,
+                mock(TaskViewTransitions.class),
+                mock(ShellExecutor.class),
+                mock(ShellExecutor.class),
+                mock(SyncTransactionQueue.class));
+        spyOn(mController);
+    }
+
+    //
+    // Shell specific
+    //
+    @Test
+    public void instantiateController_addInitCallback() {
+        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+            createController();
+            setUpTabletConfig();
+
+            verify(mShellInit, times(1)).addInitCallback(any(), any());
+        }
+    }
+
+    @Test
+    public void instantiateController_doesntAddInitCallback() {
+        if (!FLOATING_TASKS_ACTUALLY_ENABLED) {
+            createController();
+
+            verify(mShellInit, never()).addInitCallback(any(), any());
+        }
+    }
+
+    @Test
+    public void onInit_registerConfigChangeListener() {
+        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+            createController();
+            setUpTabletConfig();
+            mController.onInit();
+
+            verify(mShellController, times(1)).addConfigurationChangeListener(any());
+        }
+    }
+
+    @Test
+    public void onInit_addExternalInterface() {
+        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+            createController();
+            setUpTabletConfig();
+            mController.onInit();
+
+            verify(mShellController, times(1)).addExternalInterface(
+                    ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+        }
+    }
+
+    //
+    // Tests for floating layer, which is only available for tablets.
+    //
+
+    @Test
+    public void testIsFloatingLayerAvailable_true() {
+        createController();
+        setUpTabletConfig();
+        assertThat(mController.isFloatingLayerAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsFloatingLayerAvailable_false() {
+        createController();
+        setUpPhoneConfig();
+        assertThat(mController.isFloatingLayerAvailable()).isFalse();
+    }
+
+    //
+    // Tests for floating tasks being enabled, guarded by sysprop flag.
+    //
+
+    @Test
+    public void testIsFloatingTasksEnabled_true() {
+        createController();
+        mController.setFloatingTasksEnabled(true);
+        setUpTabletConfig();
+        assertThat(mController.isFloatingTasksEnabled()).isTrue();
+    }
+
+    @Test
+    public void testIsFloatingTasksEnabled_false() {
+        createController();
+        mController.setFloatingTasksEnabled(false);
+        setUpTabletConfig();
+        assertThat(mController.isFloatingTasksEnabled()).isFalse();
+    }
+
+    //
+    // Tests for behavior depending on flags
+    //
+
+    @Test
+    public void testShowTaskIntent_enabled() {
+        createController();
+        mController.setFloatingTasksEnabled(true);
+        setUpTabletConfig();
+
+        mController.showTask(mock(Intent.class));
+        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
+        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testShowTaskIntent_notEnabled() {
+        createController();
+        mController.setFloatingTasksEnabled(false);
+        setUpTabletConfig();
+
+        mController.showTask(mock(Intent.class));
+        verify(mWindowManager, never()).addView(any(), any());
+    }
+
+    @Test
+    public void testRemoveTask() {
+        createController();
+        mController.setFloatingTasksEnabled(true);
+        setUpTabletConfig();
+
+        mController.showTask(mock(Intent.class));
+        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
+        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
+
+        mController.removeTask();
+        verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture());
+        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 0fd5cb0..7068a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,17 +17,10 @@
 package com.android.wm.shell.freeform;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
-
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
@@ -44,9 +37,9 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,9 +58,7 @@
     @Mock
     private Transitions mTransitions;
     @Mock
-    private FullscreenTaskListener<?> mFullscreenTaskListener;
-    @Mock
-    private FreeformTaskListener<?> mFreeformTaskListener;
+    private WindowDecorViewModel mWindowDecorViewModel;
 
     private FreeformTaskTransitionObserver mTransitionObserver;
 
@@ -82,7 +73,7 @@
         doReturn(pm).when(context).getPackageManager();
 
         mTransitionObserver = new FreeformTaskTransitionObserver(
-                context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener);
+                context, mShellInit, mTransitions, mWindowDecorViewModel);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
                     Runnable.class);
@@ -112,11 +103,12 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT);
+        verify(mWindowDecorViewModel).createWindowDecoration(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
     @Test
-    public void testObtainsWindowDecorOnCloseTransition_freeform() {
+    public void testPreparesWindowDecorOnCloseTransition_freeform() {
         final TransitionInfo.Change change =
                 createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
         final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
@@ -128,7 +120,8 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+        verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     @Test
@@ -138,17 +131,13 @@
         final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
         info.addChange(change);
 
-        final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
-
         final IBinder transition = mock(IBinder.class);
         final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(windowDecor, never()).close();
+        verify(mWindowDecorViewModel, never()).destroyWindowDecoration(change.getTaskInfo());
     }
 
     @Test
@@ -159,8 +148,6 @@
         info.addChange(change);
 
         final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
 
         final IBinder transition = mock(IBinder.class);
         final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -169,7 +156,7 @@
         mTransitionObserver.onTransitionStarting(transition);
         mTransitionObserver.onTransitionFinished(transition, false);
 
-        verify(windowDecor).close();
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change.getTaskInfo());
     }
 
     @Test
@@ -192,10 +179,6 @@
         final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info2.addChange(change2);
 
-        final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
-        doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change2.getTaskInfo()), any(), any());
-
         final IBinder transition2 = mock(IBinder.class);
         final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -204,7 +187,7 @@
 
         mTransitionObserver.onTransitionFinished(transition1, false);
 
-        verify(windowDecor2).close();
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
     }
 
     @Test
@@ -215,10 +198,6 @@
         final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info1.addChange(change1);
 
-        final AutoCloseable windowDecor1 = mock(AutoCloseable.class);
-        doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change1.getTaskInfo()), any(), any());
-
         final IBinder transition1 = mock(IBinder.class);
         final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
@@ -231,10 +210,6 @@
         final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info2.addChange(change2);
 
-        final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
-        doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change2.getTaskInfo()), any(), any());
-
         final IBinder transition2 = mock(IBinder.class);
         final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -243,48 +218,8 @@
 
         mTransitionObserver.onTransitionFinished(transition1, false);
 
-        verify(windowDecor1).close();
-        verify(windowDecor2).close();
-    }
-
-    @Test
-    public void testTransfersWindowDecorOnMaximize() {
-        final TransitionInfo.Change change =
-                createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN);
-        final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0);
-        info.addChange(change);
-
-        final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
-
-        final IBinder transition = mock(IBinder.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
-        mTransitionObserver.onTransitionStarting(transition);
-
-        verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
-        verify(mFullscreenTaskListener).adoptWindowDecoration(
-                eq(change), same(startT), same(finishT), any());
-    }
-
-    @Test
-    public void testTransfersWindowDecorOnRestoreFromMaximize() {
-        final TransitionInfo.Change change =
-                createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM);
-        final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0);
-        info.addChange(change);
-
-        final IBinder transition = mock(IBinder.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
-        mTransitionObserver.onTransitionStarting(transition);
-
-        verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
-        verify(mFreeformTaskListener).adoptWindowDecoration(
-                eq(change), same(startT), same(finishT), any());
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change1.getTaskInfo());
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
     }
 
     private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index cf8297e..8ad3d2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,6 +51,7 @@
 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 org.junit.Before;
 import org.junit.Test;
@@ -176,6 +177,12 @@
     }
 
     @Test
+    public void testControllerRegisteresExternalInterface() {
+        verify(mMockShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+    }
+
+    @Test
     public void testDefaultShouldNotInOneHanded() {
         // Assert default transition state is STATE_NONE
         assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index a8d3bdc..d06fb55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -48,6 +49,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -60,6 +62,7 @@
 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 org.junit.Before;
 import org.junit.Test;
@@ -85,6 +88,7 @@
     @Mock private ShellCommandHandler mMockShellCommandHandler;
     @Mock private DisplayController mMockDisplayController;
     @Mock private PhonePipMenuController mMockPhonePipMenuController;
+    @Mock private PipAnimationController mMockPipAnimationController;
     @Mock private PipAppOpsListener mMockPipAppOpsListener;
     @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
     @Mock private PhonePipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@@ -117,8 +121,8 @@
         mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler,
                 mMockExecutor));
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
-                mShellController, mMockDisplayController, mMockPipAppOpsListener,
-                mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+                mShellController, mMockDisplayController, mMockPipAnimationController,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
                 mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
                 mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -150,6 +154,12 @@
     }
 
     @Test
+    public void instantiatePipController_registerExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+    }
+
+    @Test
     public void instantiatePipController_registerUserChangeListener() {
         verify(mShellController, times(1)).addUserChangeListener(any());
     }
@@ -183,8 +193,8 @@
 
         ShellInit shellInit = new ShellInit(mMockExecutor);
         assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
-                mShellController, mMockDisplayController, mMockPipAppOpsListener,
-                mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+                mShellController, mMockDisplayController, mMockPipAnimationController,
+                mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
                 mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
                 mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index cadfeb0..f6ac3ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
 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.isA;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -40,6 +41,7 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Rect;
@@ -52,12 +54,13 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 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.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
@@ -68,7 +71,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 /**
  * Tests for {@link RecentTasksController}.
@@ -82,14 +87,18 @@
     @Mock
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
+    private ShellController mShellController;
+    @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    @Mock
+    private ActivityTaskManager mActivityTaskManager;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
     private ShellInit mShellInit;
-    private ShellExecutor mMainExecutor;
+    private TestShellExecutor mMainExecutor;
 
     @Before
     public void setUp() {
@@ -97,8 +106,8 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
-                mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
-                mMainExecutor));
+                mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+                Optional.of(mDesktopModeTaskRepository), mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
                 mMainExecutor);
@@ -117,6 +126,12 @@
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+    }
+
+    @Test
     public void testAddRemoveSplitNotifyChange() {
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -188,10 +203,41 @@
     }
 
     @Test
+    public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
+        @SuppressWarnings("unchecked")
+        final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
+        Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+        setRawList(t1, t2, t3, t4, t5, t6);
+
+        // Mark a couple pairs [t2, t4], [t3, t5]
+        SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
+        SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+
+        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
+        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
+
+        mRecentTasksController.asRecentTasks()
+                .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+        mMainExecutor.flushAll();
+
+        assertGroupedTasksListEquals(recentTasks[0],
+                t1.taskId, -1,
+                t2.taskId, t4.taskId,
+                t3.taskId, t5.taskId,
+                t6.taskId, -1);
+    }
+
+    @Test
     public void testGetRecentTasks_groupActiveFreeformTasks() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopMode.class).startMocking();
-        when(DesktopMode.isActive(any())).thenReturn(true);
+                DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -296,7 +342,7 @@
         for (ActivityManager.RecentTaskInfo task : tasks) {
             rawList.add(task);
         }
-        doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(),
+        doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
                 anyInt());
         return rawList;
     }
@@ -307,7 +353,7 @@
      * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
      *                        the grouped task list
      */
-    private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks,
+    private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
             int... expectedTaskIds) {
         int[] flattenedTaskIds = new int[recentTasks.size() * 2];
         for (int i = 0; i < recentTasks.size(); i++) {
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 5a68361..55883ab 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
@@ -58,6 +58,7 @@
 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 org.junit.Before;
@@ -133,6 +134,15 @@
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+        when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+        mSplitScreenController.onInit();
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+    }
+
+    @Test
     public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
         doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
         doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
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 9240abf..8350870 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
@@ -16,7 +16,10 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -28,11 +31,10 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -42,8 +44,10 @@
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -115,7 +119,6 @@
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
                 mMainExecutor, Optional.empty()));
-        doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
@@ -303,4 +306,16 @@
 
         verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false));
     }
+
+    @Test
+    public void testAddActivityOptions_addsBackgroundActivitiesFlags() {
+        Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
+                SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */);
+
+        assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class),
+                mMainStage.mRootTaskInfo.token);
+        assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED));
+        assertTrue(options.getBoolean(
+                KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3..90165d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@
 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.times;
 import static org.mockito.Mockito.verify;
 
@@ -36,7 +37,9 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,25 +59,34 @@
 
     private @Mock Context mContext;
     private @Mock DisplayManager mDisplayManager;
-    private @Mock ShellInit mShellInit;
+    private @Mock ShellController mShellController;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
     private @Mock ShellExecutor mMainExecutor;
     private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
     private @Mock IconProvider mIconProvider;
     private @Mock TransactionPool mTransactionPool;
     private StartingWindowController mController;
+    private ShellInit mShellInit;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
-        mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
-                mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+        mShellInit = spy(new ShellInit(mMainExecutor));
+        mController = new StartingWindowController(mContext, mShellInit, mShellController,
+                mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+        mShellInit.init();
     }
 
     @Test
-    public void instantiate_addInitCallback() {
+    public void instantiateController_addInitCallback() {
         verify(mShellInit, times(1)).addInitCallback(any(), any());
     }
+
+    @Test
+    public void instantiateController_addExternalInterface() {
+        verify(mShellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9..fbc50c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
 
 package com.android.wm.shell.sysui;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -30,6 +34,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.After;
@@ -49,6 +54,7 @@
 public class ShellControllerTest extends ShellTestCase {
 
     private static final int TEST_USER_ID = 100;
+    private static final String EXTRA_TEST_BINDER = "test_binder";
 
     @Mock
     private ShellInit mShellInit;
@@ -81,6 +87,47 @@
     }
 
     @Test
+    public void testAddExternalInterface_ensureCallback() {
+        Binder callback = new Binder();
+        ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+            @Override
+            public void invalidate() {
+                // Do nothing
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return callback;
+            }
+        };
+        mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+        Bundle b = new Bundle();
+        mController.asShell().createExternalInterfaces(b);
+        assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+    }
+
+    @Test
+    public void testAddExternalInterface_disallowDuplicateKeys() {
+        Binder callback = new Binder();
+        ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+            @Override
+            public void invalidate() {
+                // Do nothing
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return callback;
+            }
+        };
+        mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+        assertThrows(IllegalArgumentException.class, () -> {
+            mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+        });
+    }
+
+    @Test
     public void testAddUserChangeListener_ensureCallback() {
         mController.addUserChangeListener(mUserChangeListener);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c6492be..c764741 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -45,7 +45,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -67,10 +66,12 @@
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
+import android.window.IWindowContainerToken;
 import android.window.RemoteTransition;
 import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -86,7 +87,9 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -117,18 +120,31 @@
     @Before
     public void setUp() {
         doAnswer(invocation -> invocation.getArguments()[1])
-                .when(mOrganizer).startTransition(anyInt(), any(), any());
+                .when(mOrganizer).startTransition(any(), any());
     }
 
     @Test
     public void instantiate_addInitCallback() {
         ShellInit shellInit = mock(ShellInit.class);
-        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
-                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
         verify(shellInit, times(1)).addInitCallback(any(), eq(t));
     }
 
     @Test
+    public void instantiateController_addExternalInterface() {
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        ShellController shellController = mock(ShellController.class);
+        final Transitions t = new Transitions(mContext, shellInit, shellController,
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
+        shellInit.init();
+        verify(shellController, times(1)).addExternalInterface(
+                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+    }
+
+    @Test
     public void testBasicTransitionFlow() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -136,7 +152,7 @@
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -188,7 +204,7 @@
         // Make a request that will be rejected by the testhandler.
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
         transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
                 mock(SurfaceControl.Transaction.class));
         assertEquals(1, mDefaultHandler.activeCount());
@@ -199,10 +215,12 @@
         // Make a request that will be handled by testhandler but not animated by it.
         RunningTaskInfo mwTaskInfo =
                 createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        // Make the wct non-empty.
+        handlerWCT.setFocusable(new WindowContainerToken(mock(IWindowContainerToken.class)), true);
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
         verify(mOrganizer, times(1)).startTransition(
-                eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+                eq(transitToken), eq(handlerWCT));
         transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
                 mock(SurfaceControl.Transaction.class));
         assertEquals(1, mDefaultHandler.activeCount());
@@ -217,8 +235,8 @@
         transitions.addHandler(topHandler);
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(
-                eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT));
+        verify(mOrganizer, times(2)).startTransition(
+                eq(transitToken), eq(handlerWCT));
         TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
                 .addChange(TRANSIT_CHANGE).build();
         transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
@@ -256,7 +274,7 @@
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
                         new RemoteTransition(testRemote)));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -406,7 +424,7 @@
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -1060,8 +1078,9 @@
 
     private Transitions createTestTransitions() {
         ShellInit shellInit = new ShellInit(mMainExecutor);
-        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
-                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+                mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+                mMainHandler, mAnimExecutor);
         shellInit.init();
         return t;
     }
diff --git a/libs/androidfw/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp
index 0269128..8ddc572 100644
--- a/libs/androidfw/PosixUtils.cpp
+++ b/libs/androidfw/PosixUtils.cpp
@@ -17,7 +17,7 @@
 #ifdef _WIN32
 // nothing to see here
 #else
-#include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -29,45 +29,42 @@
 
 #include "androidfw/PosixUtils.h"
 
-namespace {
-
-std::unique_ptr<std::string> ReadFile(int fd) {
-  std::unique_ptr<std::string> str(new std::string());
+static std::optional<std::string> ReadFile(int fd) {
+  std::string str;
   char buf[1024];
   ssize_t r;
   while ((r = read(fd, buf, sizeof(buf))) > 0) {
-    str->append(buf, r);
+    str.append(buf, r);
   }
   if (r != 0) {
-    return nullptr;
+    return std::nullopt;
   }
-  return str;
-}
-
+  return std::move(str);
 }
 
 namespace android {
 namespace util {
 
-std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) {
-  int stdout[2];  // stdout[0] read, stdout[1] write
+ProcResult ExecuteBinary(const std::vector<std::string>& argv) {
+  int stdout[2];  // [0] read, [1] write
   if (pipe(stdout) != 0) {
-    PLOG(ERROR) << "pipe";
-    return nullptr;
+    PLOG(ERROR) << "out pipe";
+    return ProcResult{-1};
   }
 
-  int stderr[2];  // stdout[0] read, stdout[1] write
+  int stderr[2];  // [0] read, [1] write
   if (pipe(stderr) != 0) {
-    PLOG(ERROR) << "pipe";
+    PLOG(ERROR) << "err pipe";
     close(stdout[0]);
     close(stdout[1]);
-    return nullptr;
+    return ProcResult{-1};
   }
 
   auto gid = getgid();
   auto uid = getuid();
 
-  char const** argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1));
+  // better keep no C++ objects going into the child here
+  auto argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1));
   for (size_t i = 0; i < argv.size(); i++) {
     argv0[i] = argv[i].c_str();
   }
@@ -76,8 +73,12 @@
   switch (pid) {
     case -1: // error
       free(argv0);
+      close(stdout[0]);
+      close(stdout[1]);
+      close(stderr[0]);
+      close(stderr[1]);
       PLOG(ERROR) << "fork";
-      return nullptr;
+      return ProcResult{-1};
     case 0: // child
       if (setgid(gid) != 0) {
         PLOG(ERROR) << "setgid";
@@ -109,17 +110,16 @@
       if (!WIFEXITED(status)) {
           close(stdout[0]);
           close(stderr[0]);
-          return nullptr;
+          return ProcResult{-1};
       }
-      std::unique_ptr<ProcResult> result(new ProcResult());
-      result->status = status;
-      const auto out = ReadFile(stdout[0]);
-      result->stdout_str = out ? *out : "";
+      ProcResult result(status);
+      auto out = ReadFile(stdout[0]);
+      result.stdout_str = out ? std::move(*out) : "";
       close(stdout[0]);
-      const auto err = ReadFile(stderr[0]);
-      result->stderr_str = err ? *err : "";
+      auto err = ReadFile(stderr[0]);
+      result.stderr_str = err ? std::move(*err) : "";
       close(stderr[0]);
-      return result;
+      return std::move(result);
   }
 }
 
diff --git a/libs/androidfw/include/androidfw/PosixUtils.h b/libs/androidfw/include/androidfw/PosixUtils.h
index bb20847..c46e5e6 100644
--- a/libs/androidfw/include/androidfw/PosixUtils.h
+++ b/libs/androidfw/include/androidfw/PosixUtils.h
@@ -25,12 +25,18 @@
   int status;
   std::string stdout_str;
   std::string stderr_str;
+
+  explicit ProcResult(int status) : status(status) {}
+  ProcResult(ProcResult&&) noexcept = default;
+  ProcResult& operator=(ProcResult&&) noexcept = default;
+
+  explicit operator bool() const { return status >= 0; }
 };
 
-// Fork, exec and wait for an external process. Return nullptr if the process could not be launched,
-// otherwise a ProcResult containing the external process' exit status and captured stdout and
-// stderr.
-std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv);
+// Fork, exec and wait for an external process. Returns status < 0 if the process could not be
+// launched, otherwise a ProcResult containing the external process' exit status and captured
+// stdout and stderr.
+ProcResult ExecuteBinary(const std::vector<std::string>& argv);
 
 } // namespace util
 } // namespace android
diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp
index 8c49350..097e6b0 100644
--- a/libs/androidfw/tests/PosixUtils_test.cpp
+++ b/libs/androidfw/tests/PosixUtils_test.cpp
@@ -28,27 +28,27 @@
 
 TEST(PosixUtilsTest, AbsolutePathToBinary) {
   const auto result = ExecuteBinary({"/bin/date", "--help"});
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, 0);
-  ASSERT_GE(result->stdout_str.find("usage: date "), 0);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, 0);
+  ASSERT_GE(result.stdout_str.find("usage: date "), 0);
 }
 
 TEST(PosixUtilsTest, RelativePathToBinary) {
   const auto result = ExecuteBinary({"date", "--help"});
-  ASSERT_THAT(result, NotNull());
-  ASSERT_EQ(result->status, 0);
-  ASSERT_GE(result->stdout_str.find("usage: date "), 0);
+  ASSERT_TRUE((bool)result);
+  ASSERT_EQ(result.status, 0);
+  ASSERT_GE(result.stdout_str.find("usage: date "), 0);
 }
 
 TEST(PosixUtilsTest, BadParameters) {
   const auto result = ExecuteBinary({"/bin/date", "--this-parameter-is-not-supported"});
-  ASSERT_THAT(result, NotNull());
-  ASSERT_NE(result->status, 0);
+  ASSERT_TRUE((bool)result);
+  ASSERT_GT(result.status, 0);
 }
 
 TEST(PosixUtilsTest, NoSuchBinary) {
   const auto result = ExecuteBinary({"/this/binary/does/not/exist"});
-  ASSERT_THAT(result, IsNull());
+  ASSERT_FALSE((bool)result);
 }
 
 } // android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b11e542..29f3773 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -521,6 +521,7 @@
         "Interpolator.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
+        "MemoryPolicy.cpp",
         "PathParser.cpp",
         "Properties.cpp",
         "PropertyValuesAnimatorSet.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 0759471..f06fa24 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -93,12 +93,14 @@
         case ADATASPACE_SCRGB:
             get()->mWideColorSpace = SkColorSpace::MakeSRGB();
             break;
+        default:
+            ALOGW("Unknown dataspace %d", dataspace);
+            // Treat unknown dataspaces as sRGB, so fall through
+            [[fallthrough]];
         case ADATASPACE_SRGB:
             // when sRGB is returned, it means wide color gamut is not supported.
             get()->mWideColorSpace = SkColorSpace::MakeSRGB();
             break;
-        default:
-            LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
     }
 }
 
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 7291cab..b7e9999 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -42,6 +42,8 @@
 
 namespace android::uirenderer {
 
+static constexpr auto kThreadTimeout = 60000_ms;
+
 class AHBUploader;
 // This helper uploader classes allows us to upload using either EGL or Vulkan using the same
 // interface.
@@ -80,7 +82,7 @@
     }
 
     void postIdleTimeoutCheck() {
-        mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
+        mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); });
     }
 
 protected:
@@ -97,7 +99,7 @@
 
     bool shouldTimeOutLocked() {
         nsecs_t durationSince = systemTime() - mLastUpload;
-        return durationSince > 2000_ms;
+        return durationSince > kThreadTimeout;
     }
 
     void idleTimeoutCheck() {
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
new file mode 100644
index 0000000..ca1312e7
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "MemoryPolicy.h"
+
+#include <android-base/properties.h>
+
+#include <optional>
+#include <string_view>
+
+#include "Properties.h"
+
+namespace android::uirenderer {
+
+constexpr static MemoryPolicy sDefaultMemoryPolicy;
+constexpr static MemoryPolicy sPersistentOrSystemPolicy{
+        .contextTimeout = 10_s,
+        .useAlternativeUiHidden = true,
+};
+constexpr static MemoryPolicy sLowRamPolicy{
+        .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
+};
+constexpr static MemoryPolicy sExtremeLowRam{
+        .initialMaxSurfaceAreaScale = 0.2f,
+        .surfaceSizeMultiplier = 5 * 4.0f,
+        .backgroundRetentionPercent = 0.2f,
+        .contextTimeout = 5_s,
+        .minimumResourceRetention = 1_s,
+        .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
+        .releaseContextOnStoppedOnly = true,
+};
+
+const MemoryPolicy& loadMemoryPolicy() {
+    if (Properties::isSystemOrPersistent) {
+        return sPersistentOrSystemPolicy;
+    }
+    std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, "");
+    if (memoryPolicy == "default") {
+        return sDefaultMemoryPolicy;
+    }
+    if (memoryPolicy == "lowram") {
+        return sLowRamPolicy;
+    }
+    if (memoryPolicy == "extremelowram") {
+        return sExtremeLowRam;
+    }
+
+    if (Properties::isLowRam) {
+        return sLowRamPolicy;
+    }
+    return sDefaultMemoryPolicy;
+}
+
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
new file mode 100644
index 0000000..e86b338
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"),
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "utils/TimeUtils.h"
+
+namespace android::uirenderer {
+
+// Values mirror those from ComponentCallbacks2.java
+enum class TrimLevel {
+    COMPLETE = 80,
+    MODERATE = 60,
+    BACKGROUND = 40,
+    UI_HIDDEN = 20,
+    RUNNING_CRITICAL = 15,
+    RUNNING_LOW = 10,
+    RUNNING_MODERATE = 5,
+};
+
+struct MemoryPolicy {
+    // The initial scale factor applied to the display resolution. The default is 1, but
+    // lower values may be used to start with a smaller initial cache size. The cache will
+    // be adjusted if larger frames are actually rendered
+    float initialMaxSurfaceAreaScale = 1.0f;
+    // The foreground cache size multiplier. The surface area of the screen will be multiplied
+    // by this
+    float surfaceSizeMultiplier = 12.0f * 4.0f;
+    // How much of the foreground cache size should be preserved when going into the background
+    float backgroundRetentionPercent = 0.5f;
+    // How long after the last renderer goes away before the GPU context is released. A value
+    // of 0 means only drop the context on background TRIM signals
+    nsecs_t contextTimeout = 0_ms;
+    // 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;
+    // 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 = false;
+    // Whether or not to only purge scratch resources when triggering UI Hidden or background
+    // collection
+    bool purgeScratchOnly = true;
+    // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+    bool releaseContextOnStoppedOnly = false;
+};
+
+const MemoryPolicy& loadMemoryPolicy();
+
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5a67eb9..277955e 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -87,6 +87,10 @@
 
 bool Properties::enableWebViewOverlays = true;
 
+bool Properties::isHighEndGfx = true;
+bool Properties::isLowRam = false;
+bool Properties::isSystemOrPersistent = false;
+
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2f8c679..96a5176 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -193,6 +193,8 @@
  */
 #define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled"
 
+#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -292,16 +294,27 @@
 
     static bool enableWebViewOverlays;
 
+    static bool isHighEndGfx;
+    static bool isLowRam;
+    static bool isSystemOrPersistent;
+
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
     }
 
     static void setIsHighEndGfx(bool isHighEndGfx) {
+        Properties::isHighEndGfx = isHighEndGfx;
         stretchEffectBehavior = isHighEndGfx ?
             StretchEffectBehavior::ShaderHWUI :
             StretchEffectBehavior::UniformScale;
     }
 
+    static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; }
+
+    static void setIsSystemOrPersistent(bool isSystemOrPersistent) {
+        Properties::isSystemOrPersistent = isSystemOrPersistent;
+    }
+
     /**
      * Used for testing. Typical configuration of stretch behavior is done
      * through setIsHighEndGfx
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4f281fc..704fba9 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -271,6 +271,16 @@
     Properties::setIsHighEndGfx(jIsHighEndGfx);
 }
 
+static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz,
+                                                      jboolean isLowRam) {
+    Properties::setIsLowRam(isLowRam);
+}
+
+static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz,
+                                                                  jboolean isSystemOrPersistent) {
+    Properties::setIsSystemOrPersistent(isSystemOrPersistent);
+}
+
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
                                                           jlong proxyPtr, jlongArray frameInfo,
                                                           jint frameInfoSize) {
@@ -949,6 +959,9 @@
         {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
         {"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
         {"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx},
+        {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam},
+        {"nSetIsSystemOrPersistent", "(Z)V",
+         (void*)android_view_ThreadedRenderer_setIsSystemOrPersistent},
         {"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame},
         {"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy},
         {"nRegisterAnimatingRenderNode", "(JJ)V",
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index ded2b06..1d24e71 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -16,6 +16,16 @@
 
 #include "CacheManager.h"
 
+#include <GrContextOptions.h>
+#include <SkExecutor.h>
+#include <SkGraphics.h>
+#include <SkMathPriv.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+#include <set>
+
+#include "CanvasContext.h"
 #include "DeviceInfo.h"
 #include "Layer.h"
 #include "Properties.h"
@@ -25,40 +35,34 @@
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
-#include <utils/Trace.h>
-
-#include <GrContextOptions.h>
-#include <SkExecutor.h>
-#include <SkGraphics.h>
-#include <SkMathPriv.h>
-#include <math.h>
-#include <set>
 
 namespace android {
 namespace uirenderer {
 namespace renderthread {
 
-// This multiplier was selected based on historical review of cache sizes relative
-// to the screen resolution. This is meant to be a conservative default based on
-// that analysis. The 4.0f is used because the default pixel format is assumed to
-// be ARGB_8888.
-#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
-#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
+CacheManager::CacheManager(RenderThread& thread)
+        : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
+    mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
+                                          mMemoryPolicy.initialMaxSurfaceAreaScale);
+    setupCacheLimits();
+}
 
-CacheManager::CacheManager()
-        : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight())
-        , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER)
-        , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE)
-        // This sets the maximum size for a single texture atlas in the GPU font cache. If
-        // necessary, the cache can allocate additional textures that are counted against the
-        // total cache limits provided to Skia.
-        , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea))
-        // This sets the maximum size of the CPU font cache to be at least the same size as the
-        // total number of GPU font caches (i.e. 4 separate GPU atlases).
-        , mMaxCpuFontCacheBytes(
-                  std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()))
-        , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
+void CacheManager::setupCacheLimits() {
+    mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
+    mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
+    // This sets the maximum size for a single texture atlas in the GPU font cache. If
+    // necessary, the cache can allocate additional textures that are counted against the
+    // total cache limits provided to Skia.
+    mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea);
+    // This sets the maximum size of the CPU font cache to be at least the same size as the
+    // total number of GPU font caches (i.e. 4 separate GPU atlases).
+    mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
+    mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
+
     SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
+    if (mGrContext) {
+        mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+    }
 }
 
 void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +73,7 @@
     if (context) {
         mGrContext = std::move(context);
         mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+        mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
     }
 }
 
@@ -96,7 +101,7 @@
     contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
 }
 
-void CacheManager::trimMemory(TrimMemoryMode mode) {
+void CacheManager::trimMemory(TrimLevel mode) {
     if (!mGrContext) {
         return;
     }
@@ -104,21 +109,28 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(/*syncCpu=*/true);
 
+    if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
+        mode = TrimLevel::COMPLETE;
+    }
+
     switch (mode) {
-        case TrimMemoryMode::Complete:
+        case TrimLevel::COMPLETE:
             mGrContext->freeGpuResources();
             SkGraphics::PurgeAllCaches();
+            mRenderThread.destroyRenderingContext();
             break;
-        case TrimMemoryMode::UiHidden:
+        case TrimLevel::UI_HIDDEN:
             // Here we purge all the unlocked scratch resources and then toggle the resources cache
             // limits between the background and max amounts. This causes the unlocked resources
             // that have persistent data to be purged in LRU order.
-            mGrContext->purgeUnlockedResources(true);
             mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
-            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+            mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
             break;
+        default:
+            break;
     }
 }
 
@@ -147,11 +159,29 @@
 }
 
 void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+    log.appendFormat(R"(Memory policy:
+  Max surface area: %zu
+  Max resource usage: %.2fMB (x%.0f)
+  Background retention: %.0f%% (altUiHidden = %s)
+)",
+                     mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
+                     mMemoryPolicy.surfaceSizeMultiplier,
+                     mMemoryPolicy.backgroundRetentionPercent * 100.0f,
+                     mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
+    if (Properties::isSystemOrPersistent) {
+        log.appendFormat("  IsSystemOrPersistent\n");
+    }
+    log.appendFormat("  GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
+    size_t stoppedContexts = 0;
+    for (auto context : mCanvasContexts) {
+        if (context->isStopped()) stoppedContexts++;
+    }
+    log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+
     if (!mGrContext) {
-        log.appendFormat("No valid cache instance.\n");
+        log.appendFormat("No GPU context.\n");
         return;
     }
-
     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
             {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
             {"skia/sk_resource_cache/rrect-blur_", "Masks"},
@@ -199,6 +229,8 @@
 }
 
 void CacheManager::onFrameCompleted() {
+    cancelDestroyContext();
+    mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
     if (ATRACE_ENABLED()) {
         static skiapipeline::ATraceMemoryDump tracer;
         tracer.startFrame();
@@ -210,11 +242,82 @@
     }
 }
 
-void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) {
-    if (mGrContext) {
-        mGrContext->performDeferredCleanup(
-            std::chrono::milliseconds(cleanupOlderThanMillis),
-            /* scratchResourcesOnly */true);
+void CacheManager::onThreadIdle() {
+    if (!mGrContext || mFrameCompletions.size() == 0) return;
+
+    const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+    // Rate limiting
+    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));
+        mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
+                                           mMemoryPolicy.purgeScratchOnly);
+    }
+}
+
+void CacheManager::scheduleDestroyContext() {
+    if (mMemoryPolicy.contextTimeout > 0) {
+        mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
+                                          [this, genId = mGenerationId] {
+                                              if (mGenerationId != genId) return;
+                                              // GenID should have already stopped this, but just in
+                                              // case
+                                              if (!areAllContextsStopped()) return;
+                                              mRenderThread.destroyRenderingContext();
+                                          });
+    }
+}
+
+void CacheManager::cancelDestroyContext() {
+    if (mIsDestructionPending) {
+        mIsDestructionPending = false;
+        mGenerationId++;
+    }
+}
+
+bool CacheManager::areAllContextsStopped() {
+    for (auto context : mCanvasContexts) {
+        if (!context->isStopped()) return false;
+    }
+    return true;
+}
+
+void CacheManager::checkUiHidden() {
+    if (!mGrContext) return;
+
+    if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
+        trimMemory(TrimLevel::UI_HIDDEN);
+    }
+}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {
+    mCanvasContexts.push_back(context);
+    cancelDestroyContext();
+}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {
+    std::erase(mCanvasContexts, context);
+    checkUiHidden();
+    if (mCanvasContexts.empty()) {
+        scheduleDestroyContext();
+    }
+}
+
+void CacheManager::onContextStopped(CanvasContext* context) {
+    checkUiHidden();
+    if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
+        scheduleDestroyContext();
+    }
+}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {
+    int frameArea = width * height;
+    if (frameArea > mMaxSurfaceArea) {
+        mMaxSurfaceArea = frameArea;
+        setupCacheLimits();
     }
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index af82672..d21ac9b 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -22,7 +22,11 @@
 #endif
 #include <SkSurface.h>
 #include <utils/String8.h>
+
 #include <vector>
+
+#include "MemoryPolicy.h"
+#include "utils/RingBuffer.h"
 #include "utils/TimeUtils.h"
 
 namespace android {
@@ -35,17 +39,15 @@
 
 namespace renderthread {
 
-class IRenderPipeline;
 class RenderThread;
+class CanvasContext;
 
 class CacheManager {
 public:
-    enum class TrimMemoryMode { Complete, UiHidden };
-
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
 #endif
-    void trimMemory(TrimMemoryMode mode);
+    void trimMemory(TrimLevel mode);
     void trimStaleResources();
     void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
     void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
@@ -53,30 +55,50 @@
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
     void onFrameCompleted();
+    void notifyNextFrameSize(int width, int height);
 
-    void performDeferredCleanup(nsecs_t cleanupOlderThanMillis);
+    void onThreadIdle();
+
+    void registerCanvasContext(CanvasContext* context);
+    void unregisterCanvasContext(CanvasContext* context);
+    void onContextStopped(CanvasContext* context);
 
 private:
     friend class RenderThread;
 
-    explicit CacheManager();
+    explicit CacheManager(RenderThread& thread);
+    void setupCacheLimits();
+    bool areAllContextsStopped();
+    void checkUiHidden();
+    void scheduleDestroyContext();
+    void cancelDestroyContext();
 
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     void reset(sk_sp<GrDirectContext> grContext);
 #endif
     void destroy();
 
-    const size_t mMaxSurfaceArea;
+    RenderThread& mRenderThread;
+    const MemoryPolicy& mMemoryPolicy;
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     sk_sp<GrDirectContext> mGrContext;
 #endif
 
-    const size_t mMaxResourceBytes;
-    const size_t mBackgroundResourceBytes;
+    size_t mMaxSurfaceArea = 0;
 
-    const size_t mMaxGpuFontAtlasBytes;
-    const size_t mMaxCpuFontCacheBytes;
-    const size_t mBackgroundCpuFontCacheBytes;
+    size_t mMaxResourceBytes = 0;
+    size_t mBackgroundResourceBytes = 0;
+
+    size_t mMaxGpuFontAtlasBytes = 0;
+    size_t mMaxCpuFontCacheBytes = 0;
+    size_t mBackgroundCpuFontCacheBytes = 0;
+
+    std::vector<CanvasContext*> mCanvasContexts;
+    RingBuffer<uint64_t, 100> mFrameCompletions;
+
+    nsecs_t mLastDeferredCleanup = 0;
+    bool mIsDestructionPending = false;
+    uint32_t mGenerationId = 0;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 75d3ff7..6a0c5a8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -42,9 +42,6 @@
 #include "utils/GLUtils.h"
 #include "utils/TimeUtils.h"
 
-#define TRIM_MEMORY_COMPLETE 80
-#define TRIM_MEMORY_UI_HIDDEN 20
-
 #define LOG_FRAMETIME_MMA 0
 
 #if LOG_FRAMETIME_MMA
@@ -122,6 +119,7 @@
         , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
         , mContentDrawBounds(0, 0, 0, 0)
         , mRenderPipeline(std::move(renderPipeline)) {
+    mRenderThread.cacheManager().registerCanvasContext(this);
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
@@ -133,6 +131,7 @@
         node->clearRoot();
     }
     mRenderNodes.clear();
+    mRenderThread.cacheManager().unregisterCanvasContext(this);
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -154,6 +153,7 @@
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
+    mRenderThread.cacheManager().onContextStopped(this);
 }
 
 static void setBufferCount(ANativeWindow* window) {
@@ -251,6 +251,7 @@
             mGenerationID++;
             mRenderThread.removeFrameCallback(this);
             mRenderPipeline->onStop();
+            mRenderThread.cacheManager().onContextStopped(this);
         } else if (mIsDirty && hasSurface()) {
             mRenderThread.postFrameCallback(this);
         }
@@ -461,7 +462,6 @@
 }
 
 void CanvasContext::stopDrawing() {
-    cleanupResources();
     mRenderThread.removeFrameCallback(this);
     mAnimationContext->pauseAnimators();
     mGenerationID++;
@@ -648,25 +648,10 @@
         }
     }
 
-    cleanupResources();
     mRenderThread.cacheManager().onFrameCompleted();
     return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
 }
 
-void CanvasContext::cleanupResources() {
-    auto& tracker = mJankTracker.frames();
-    auto size = tracker.size();
-    auto capacity = tracker.capacity();
-    if (size == capacity) {
-        nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC);
-        nsecs_t frameCompleteNanos =
-            tracker[0].get(FrameInfoIndex::FrameCompleted);
-        nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos;
-        nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s));
-        mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis);
-    }
-}
-
 void CanvasContext::reportMetricsWithPresentTime() {
     {  // acquire lock
         std::scoped_lock lock(mFrameMetricsReporterMutex);
@@ -790,6 +775,7 @@
     SkISize size;
     size.fWidth = ANativeWindow_getWidth(anw);
     size.fHeight = ANativeWindow_getHeight(anw);
+    mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight);
     return size;
 }
 
@@ -868,18 +854,6 @@
     }
 }
 
-void CanvasContext::trimMemory(RenderThread& thread, int level) {
-    ATRACE_CALL();
-    if (!thread.getGrContext()) return;
-    ATRACE_CALL();
-    if (level >= TRIM_MEMORY_COMPLETE) {
-        thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
-        thread.destroyRenderingContext();
-    } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
-        thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
-    }
-}
-
 DeferredLayerUpdater* CanvasContext::createTextureLayer() {
     return mRenderPipeline->createTextureLayer();
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 951ee21..748ab96 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -127,6 +127,7 @@
     void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
+    bool isStopped() { return mStopped || !hasSurface(); }
     bool hasSurface() const { return mNativeSurface.get(); }
     void allocateBuffers();
 
@@ -148,7 +149,6 @@
     void markLayerInUse(RenderNode* node);
 
     void destroyHardwareResources();
-    static void trimMemory(RenderThread& thread, int level);
 
     DeferredLayerUpdater* createTextureLayer();
 
@@ -330,8 +330,6 @@
 
     std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
     std::function<void()> mPrepareSurfaceControlForWebviewCallback;
-
-    void cleanupResources();
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 59c914f..03f02de 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -243,7 +243,9 @@
     mContext->unpinImages();
 
     for (size_t i = 0; i < mLayers.size(); i++) {
-        mLayers[i]->apply();
+        if (mLayers[i]) {
+            mLayers[i]->apply();
+        }
     }
     mLayers.clear();
     mContext->setContentDrawBounds(mContentDrawBounds);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 40a0bac..3324715 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -196,7 +196,8 @@
     // Avoid creating a RenderThread to do a trimMemory.
     if (RenderThread::hasInstance()) {
         RenderThread& thread = RenderThread::getInstance();
-        thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); });
+        const auto trimLevel = static_cast<TrimLevel>(level);
+        thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); });
     }
 }
 
@@ -205,7 +206,7 @@
         RenderThread& thread = RenderThread::getInstance();
         thread.queue().post([&thread]() {
             if (thread.getGrContext()) {
-                thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+                thread.cacheManager().trimMemory(TrimLevel::COMPLETE);
             }
         });
     }
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 3ff4081..7a7f1ab 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -251,7 +251,7 @@
     mEglManager = new EglManager();
     mRenderState = new RenderState(*this);
     mVkManager = VulkanManager::getInstance();
-    mCacheManager = new CacheManager();
+    mCacheManager = new CacheManager(*this);
 }
 
 void RenderThread::setupFrameInterval() {
@@ -453,6 +453,8 @@
             // next vsync (oops), so none of the callbacks are run.
             requestVsync();
         }
+
+        mCacheManager->onThreadIdle();
     }
 
     return false;
@@ -502,6 +504,11 @@
     HardwareBitmapUploader::initialize();
 }
 
+void RenderThread::trimMemory(TrimLevel level) {
+    ATRACE_CALL();
+    cacheManager().trimMemory(level);
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index c1f6790..0a89e5e 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,11 +17,11 @@
 #ifndef RENDERTHREAD_H_
 #define RENDERTHREAD_H_
 
-#include <surface_control_private.h>
 #include <GrDirectContext.h>
 #include <SkBitmap.h>
 #include <cutils/compiler.h>
 #include <private/android/choreographer.h>
+#include <surface_control_private.h>
 #include <thread/ThreadBase.h>
 #include <utils/Looper.h>
 #include <utils/Thread.h>
@@ -31,6 +31,7 @@
 #include <set>
 
 #include "CacheManager.h"
+#include "MemoryPolicy.h"
 #include "ProfileDataContainer.h"
 #include "RenderTask.h"
 #include "TimeLord.h"
@@ -172,6 +173,8 @@
         return mASurfaceControlFunctions;
     }
 
+    void trimMemory(TrimLevel level);
+
     /**
      * isCurrent provides a way to query, if the caller is running on
      * the render thread.
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 898c64b..0faa8f4 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -28,7 +28,10 @@
 #if HWUI_NULL_GPU
         info.density = 2.f;
 #else
-        const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
+        const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
+
+        const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
         LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
 
         const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info);
@@ -48,7 +51,10 @@
         config.xDpi = config.yDpi = 320.f;
         config.refreshRate = 60.f;
 #else
-        const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
+        const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
+
+        const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
         LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
 
         const status_t status = SurfaceComposerClient::getActiveDisplayMode(token, &config);
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index edd3e4e..df06ead 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -58,7 +58,7 @@
     ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
 
     // attempt to trim all memory while we still hold strong refs
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+    renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
 
     // free the surfaces
@@ -75,11 +75,11 @@
     ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
 
     // UI hidden and make sure only some got purged (unique should remain)
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+    renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN);
     ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
     ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
 
     // complete and make sure all get purged
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+    renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
 }
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index 547d719..ff1714d 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -133,4 +133,6 @@
     static_libs: [
         "libgmock",
     ],
+
+    host_required: ["compatibility-tradefed"],
 }
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 71cb0e3..5cf1067 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -31,6 +31,18 @@
  * This class contains extra parameters to pass in a GNSS measurement request.
  */
 public final class GnssMeasurementRequest implements Parcelable {
+    /**
+     * Represents a passive only request. Such a request will not trigger any active GNSS
+     * measurements or power usage itself, but may receive GNSS measurements generated in response
+     * to other requests.
+     *
+     * <p class="note">Note that on Android T, such a request will trigger one GNSS measurement.
+     * Another GNSS measurement will be triggered after {@link #PASSIVE_INTERVAL} and so on.
+     *
+     * @see GnssMeasurementRequest#getIntervalMillis()
+     */
+    public static final int PASSIVE_INTERVAL = Integer.MAX_VALUE;
+
     private final boolean mCorrelationVectorOutputsEnabled;
     private final boolean mFullTracking;
     private final int mIntervalMillis;
@@ -76,12 +88,19 @@
     }
 
     /**
-     * Represents the requested time interval between the reported measurements in milliseconds.
+     * Returns the requested time interval between the reported measurements in milliseconds, or
+     * {@link #PASSIVE_INTERVAL} if this is a passive, no power request. A passive request will not
+     * actively generate GNSS measurement updates, but may receive GNSS measurement updates
+     * generated as a result of other GNSS measurement requests.
      *
      * <p>If the time interval is not set, the default value is 0, which means the fastest rate the
      * GNSS chipset can report.
      *
      * <p>The GNSS chipset may report measurements with a rate faster than requested.
+     *
+     * <p class="note">Note that on Android T, a request interval of {@link #PASSIVE_INTERVAL}
+     * will first trigger one GNSS measurement. Another GNSS measurement will be triggered after
+     * {@link #PASSIVE_INTERVAL} milliseconds ans so on.
      */
     public @IntRange(from = 0) int getIntervalMillis() {
         return mIntervalMillis;
@@ -213,11 +232,17 @@
 
         /**
          * Set the time interval between the reported measurements in milliseconds, which is 0 by
-         * default.
+         * default. The request interval may be set to {@link #PASSIVE_INTERVAL} which indicates
+         * this request will not actively generate GNSS measurement updates, but may receive
+         * GNSS measurement updates generated as a result of other GNSS measurement requests.
          *
          * <p>An interval of 0 milliseconds means the fastest rate the chipset can report.
          *
          * <p>The GNSS chipset may report measurements with a rate faster than requested.
+         *
+         * <p class="note">Note that on Android T, a request interval of {@link #PASSIVE_INTERVAL}
+         * will first trigger one GNSS measurement. Another GNSS measurement will be triggered after
+         * {@link #PASSIVE_INTERVAL} milliseconds and so on.
          */
         @NonNull public Builder setIntervalMillis(@IntRange(from = 0) int value) {
             mIntervalMillis = Preconditions.checkArgumentInRange(value, 0, Integer.MAX_VALUE,
diff --git a/media/Android.bp b/media/Android.bp
index e97f077..97970da 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -90,8 +90,13 @@
         "aidl/android/media/audio/common/AudioStreamType.aidl",
         "aidl/android/media/audio/common/AudioUsage.aidl",
         "aidl/android/media/audio/common/AudioUuid.aidl",
+        "aidl/android/media/audio/common/Boolean.aidl",
+        "aidl/android/media/audio/common/Byte.aidl",
         "aidl/android/media/audio/common/ExtraAudioDescriptor.aidl",
+        "aidl/android/media/audio/common/Float.aidl",
+        "aidl/android/media/audio/common/Double.aidl",
         "aidl/android/media/audio/common/Int.aidl",
+        "aidl/android/media/audio/common/Long.aidl",
         "aidl/android/media/audio/common/PcmType.aidl",
     ],
     stability: "vintf",
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/media/aidl/android/media/audio/common/Boolean.aidl
similarity index 67%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to media/aidl/android/media/audio/common/Boolean.aidl
index 9b370d8..fddd5324 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/media/aidl/android/media/audio/common/Boolean.aidl
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package android.media.audio.common;
 
 /**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * This is a simple wrapper around a 'boolean', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
  */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Boolean {
+    boolean value;
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/media/aidl/android/media/audio/common/Byte.aidl
similarity index 68%
rename from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
rename to media/aidl/android/media/audio/common/Byte.aidl
index 9b370d8..f0a31a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/media/aidl/android/media/audio/common/Byte.aidl
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package android.media.audio.common;
 
 /**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * This is a simple wrapper around a 'byte', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
  */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Byte {
+    byte value;
 }
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/media/aidl/android/media/audio/common/Double.aidl
similarity index 68%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to media/aidl/android/media/audio/common/Double.aidl
index 104bf99..d7ab7b8 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/media/aidl/android/media/audio/common/Double.aidl
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
-
-import android.app.cloudsearch.SearchRequest;
+package android.media.audio.common;
 
 /**
- * Interface from the system to CloudSearch service.
+ * This is a simple wrapper around a 'double', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
  *
- * @hide
+ * {@hide}
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Double {
+    double value;
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/media/aidl/android/media/audio/common/Float.aidl
similarity index 68%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to media/aidl/android/media/audio/common/Float.aidl
index 9b370d8..4c5257e 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/media/aidl/android/media/audio/common/Float.aidl
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package android.media.audio.common;
 
 /**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * This is a simple wrapper around a 'float', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
  */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Float {
+    float value;
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/media/aidl/android/media/audio/common/Long.aidl
similarity index 68%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to media/aidl/android/media/audio/common/Long.aidl
index 9b370d8..a4aeb53 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/media/aidl/android/media/audio/common/Long.aidl
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package android.media.audio.common;
 
 /**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * This is a simple wrapper around a 'long', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
  */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Long {
+    long value;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
new file mode 100644
index 0000000..bc996e4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Boolean {
+  boolean value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
new file mode 100644
index 0000000..604e74d
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Byte {
+  byte value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
new file mode 100644
index 0000000..a525629
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Double {
+  double value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
new file mode 100644
index 0000000..af98eab
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Float {
+  float value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
new file mode 100644
index 0000000..e403dd3
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Long {
+  long value;
+}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index d06bbc6..980acca 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -516,6 +516,9 @@
     /** Output channel mask for 5.1 */
     public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
             CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
+    /** Output channel mask for 6.1
+     *  Same as 5.1 with the addition of the back center channel */
+    public static final int CHANNEL_OUT_6POINT1 = (CHANNEL_OUT_5POINT1 | CHANNEL_OUT_BACK_CENTER);
     /** @hide */
     public static final int CHANNEL_OUT_5POINT1_SIDE = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
             CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY |
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index be9862b..49b314d 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -242,13 +242,14 @@
      * To decode such an image, {@link MediaCodec} decoder for
      * {@link #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
      * the correct {@link #MediaFormat} based on additional information in
-     * the track format, and send it to {@link MediaCodec#configure}.
+     * the track format (shown in the next paragraph), and send it to
+     * {@link MediaCodec#configure}.
      *
      * The track's MediaFormat will come with {@link #KEY_WIDTH} and
      * {@link #KEY_HEIGHT} keys, which describes the width and height
      * of the image. If the image doesn't contain grid (i.e. none of
      * {@link #KEY_TILE_WIDTH}, {@link #KEY_TILE_HEIGHT},
-     * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present}), the
+     * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present), the
      * track will contain a single sample of coded data for the entire image,
      * and the image width and height should be used to set up the decoder.
      *
@@ -266,6 +267,36 @@
     public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
 
     /**
+     * MIME type for AVIF still image data encoded in AV1.
+     *
+     * To decode such an image, {@link MediaCodec} decoder for
+     * {@link #MIMETYPE_VIDEO_AV1} shall be used. The client needs to form
+     * the correct {@link #MediaFormat} based on additional information in
+     * the track format (shown in the next paragraph), and send it to
+     * {@link MediaCodec#configure}.
+     *
+     * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+     * {@link #KEY_HEIGHT} keys, which describes the width and height
+     * of the image. If the image doesn't contain grid (i.e. none of
+     * {@link #KEY_TILE_WIDTH}, {@link #KEY_TILE_HEIGHT},
+     * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present), the
+     * track will contain a single sample of coded data for the entire image,
+     * and the image width and height should be used to set up the decoder.
+     *
+     * If the image does come with grid, each sample from the track will
+     * contain one tile in the grid, of which the size is described by
+     * {@link #KEY_TILE_WIDTH} and {@link #KEY_TILE_HEIGHT}. This size
+     * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+     * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+     * by {@link #KEY_GRID_COLUMNS} samples in row-major, top-row first,
+     * left-to-right order. The output image should be reconstructed by
+     * first tiling the decoding results of the tiles in the correct order,
+     * then trimming (before rotation is applied) on the bottom and right
+     * side, if the tiled area is larger than the image width and height.
+     */
+    public static final String MIMETYPE_IMAGE_AVIF = "image/avif";
+
+    /**
      * MIME type for WebVTT subtitle data.
      */
     public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -485,9 +516,11 @@
 
     /**
      * A key describing the width (in pixels) of each tile of the content in a
-     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+     * The associated value is an integer.
      *
-     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+     * instructions of such tracks.
      *
      * @see #KEY_TILE_HEIGHT
      * @see #KEY_GRID_ROWS
@@ -497,9 +530,11 @@
 
     /**
      * A key describing the height (in pixels) of each tile of the content in a
-     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+     * The associated value is an integer.
      *
-     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+     * instructions of such tracks.
      *
      * @see #KEY_TILE_WIDTH
      * @see #KEY_GRID_ROWS
@@ -509,9 +544,11 @@
 
     /**
      * A key describing the number of grid rows in the content in a
-     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+     * The associated value is an integer.
      *
-     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+     * instructions of such tracks.
      *
      * @see #KEY_TILE_WIDTH
      * @see #KEY_TILE_HEIGHT
@@ -521,9 +558,11 @@
 
     /**
      * A key describing the number of grid columns in the content in a
-     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+     * The associated value is an integer.
      *
-     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+     * instructions of such tracks.
      *
      * @see #KEY_TILE_WIDTH
      * @see #KEY_TILE_HEIGHT
@@ -1350,9 +1389,9 @@
      * selected in the absence of a specific user choice.
      * This is currently used in two scenarios:
      * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
-     * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
-     * primary item in the file.
-
+     * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track,
+     * indicating the image is the primary item in the file.
+     *
      * The associated value is an integer, where non-0 means TRUE.  This is an optional
      * field; if not specified, DEFAULT is considered to be FALSE.
      */
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 283a41a..5781537 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -28,11 +28,13 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 
@@ -622,6 +624,58 @@
         return true;
     }
 
+    /**
+     * Dumps the current state of the object to the given {@code pw} as a human-readable string.
+     *
+     * <p> Used in the context of dumpsys. </p>
+     *
+     * @hide
+     */
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "MediaRoute2Info");
+
+        String indent = prefix + "  ";
+
+        pw.println(indent + "mId=" + mId);
+        pw.println(indent + "mName=" + mName);
+        pw.println(indent + "mFeatures=" + mFeatures);
+        pw.println(indent + "mIsSystem=" + mIsSystem);
+        pw.println(indent + "mIconUri=" + mIconUri);
+        pw.println(indent + "mDescription=" + mDescription);
+        pw.println(indent + "mConnectionState=" + mConnectionState);
+        pw.println(indent + "mClientPackageName=" + mClientPackageName);
+        pw.println(indent + "mPackageName=" + mPackageName);
+
+        dumpVolume(pw, indent);
+
+        pw.println(indent + "mAddress=" + mAddress);
+        pw.println(indent + "mDeduplicationIds=" + mDeduplicationIds);
+        pw.println(indent + "mExtras=" + mExtras);
+        pw.println(indent + "mProviderId=" + mProviderId);
+    }
+
+    private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
+        String volumeHandlingName;
+
+        switch (mVolumeHandling) {
+            case PLAYBACK_VOLUME_FIXED:
+                volumeHandlingName = "FIXED";
+                break;
+            case PLAYBACK_VOLUME_VARIABLE:
+                volumeHandlingName = "VARIABLE";
+                break;
+            default:
+                volumeHandlingName = "UNKNOWN";
+                break;
+        }
+
+        String volume = String.format(Locale.US,
+                "volume(current=%d, max=%d, handling=%s(%d))",
+                mVolume, mVolumeMax, volumeHandlingName, mVolumeHandling);
+
+        pw.println(prefix + volume);
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 207460a..71d8261 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -180,6 +182,24 @@
         dest.writeBundle(mExtras);
     }
 
+    /**
+     * Dumps current state of the instance. Use with {@code dumpsys}.
+     *
+     * See {@link android.os.Binder#dump(FileDescriptor, PrintWriter, String[])}.
+     *
+     * @hide
+     */
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "RouteDiscoveryPreference");
+
+        String indent = prefix + "  ";
+
+        pw.println(indent + "mPreferredFeatures=" + mPreferredFeatures);
+        pw.println(indent + "mPackageOrder=" + mPackageOrder);
+        pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
+        pw.println(indent + "mExtras=" + mExtras);
+    }
+
     @Override
     public String toString() {
         StringBuilder result = new StringBuilder()
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 913ac6b..10973ab 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -25,6 +25,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -327,6 +329,34 @@
         dest.writeBoolean(mIsSystemSession);
     }
 
+    /**
+     * Dumps current state of the instance. Use with {@code dumpsys}.
+     *
+     * See {@link android.os.Binder#dump(FileDescriptor, PrintWriter, String[])}.
+     *
+     * @hide
+     */
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "RoutingSessionInfo");
+
+        String indent = prefix + "  ";
+
+        pw.println(indent + "mId=" + mId);
+        pw.println(indent + "mName=" + mName);
+        pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
+        pw.println(indent + "mClientPackageName=" + mClientPackageName);
+        pw.println(indent + "mProviderId=" + mProviderId);
+        pw.println(indent + "mSelectedRoutes=" + mSelectedRoutes);
+        pw.println(indent + "mSelectableRoutes=" + mSelectableRoutes);
+        pw.println(indent + "mDeselectableRoutes=" + mDeselectableRoutes);
+        pw.println(indent + "mTransferableRoutes=" + mTransferableRoutes);
+        pw.println(indent + "mVolumeHandling=" + mVolumeHandling);
+        pw.println(indent + "mVolumeMax=" + mVolumeMax);
+        pw.println(indent + "mVolume=" + mVolume);
+        pw.println(indent + "mControlHints=" + mControlHints);
+        pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 491a8ec..87634aa 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -355,13 +355,7 @@
                         case AudioChannelLayout.LAYOUT_5POINT1POINT4:
                             return AudioFormat.CHANNEL_OUT_5POINT1POINT4;
                         case AudioChannelLayout.LAYOUT_6POINT1:
-                            return AudioFormat.CHANNEL_OUT_FRONT_LEFT
-                                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT
-                                    | AudioFormat.CHANNEL_OUT_FRONT_CENTER
-                                    | AudioFormat.CHANNEL_OUT_LOW_FREQUENCY
-                                    | AudioFormat.CHANNEL_OUT_BACK_LEFT
-                                    | AudioFormat.CHANNEL_OUT_BACK_RIGHT
-                                    | AudioFormat.CHANNEL_OUT_BACK_CENTER;
+                            return AudioFormat.CHANNEL_OUT_6POINT1;
                         case AudioChannelLayout.LAYOUT_7POINT1:
                             return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
                         case AudioChannelLayout.LAYOUT_7POINT1POINT2:
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 1f89f99..6aead43 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -30,8 +30,10 @@
 
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 
 
 /**
@@ -50,10 +52,10 @@
 @SystemApi
 public class AudioMixingRule {
 
-    private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria,
+    private AudioMixingRule(int mixType, Collection<AudioMixMatchCriterion> criteria,
                             boolean allowPrivilegedMediaPlaybackCapture,
                             boolean voiceCommunicationCaptureAllowed) {
-        mCriteria = criteria;
+        mCriteria = new ArrayList<>(criteria);
         mTargetMixType = mixType;
         mAllowPrivilegedPlaybackCapture = allowPrivilegedMediaPlaybackCapture;
         mVoiceCommunicationCaptureAllowed = voiceCommunicationCaptureAllowed;
@@ -140,6 +142,20 @@
             return Objects.hash(mAttr, mIntProp, mRule);
         }
 
+        @Override
+        public boolean equals(Object object) {
+            if (object == null || this.getClass() != object.getClass()) {
+                return false;
+            }
+            if (object == this) {
+                return true;
+            }
+            AudioMixMatchCriterion other = (AudioMixMatchCriterion) object;
+            return mRule == other.mRule
+                    && mIntProp == other.mIntProp
+                    && Objects.equals(mAttr, other.mAttr);
+        }
+
         void writeToParcel(Parcel dest) {
             dest.writeInt(mRule);
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
@@ -192,15 +208,6 @@
         return false;
     }
 
-    private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
-            ArrayList<AudioMixMatchCriterion> cr2) {
-        if (cr1 == null || cr2 == null) return false;
-        if (cr1 == cr2) return true;
-        if (cr1.size() != cr2.size()) return false;
-        //TODO iterate over rules to check they contain the same criterion
-        return (cr1.hashCode() == cr2.hashCode());
-    }
-
     private final int mTargetMixType;
     int getTargetMixType() {
         return mTargetMixType;
@@ -286,9 +293,9 @@
 
         final AudioMixingRule that = (AudioMixingRule) o;
         return (this.mTargetMixType == that.mTargetMixType)
-                && (areCriteriaEquivalent(this.mCriteria, that.mCriteria)
-                && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture
-                && this.mVoiceCommunicationCaptureAllowed
+                && Objects.equals(mCriteria, that.mCriteria)
+                && (this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture)
+                && (this.mVoiceCommunicationCaptureAllowed
                     == that.mVoiceCommunicationCaptureAllowed);
     }
 
@@ -372,7 +379,7 @@
      * Builder class for {@link AudioMixingRule} objects
      */
     public static class Builder {
-        private ArrayList<AudioMixMatchCriterion> mCriteria;
+        private final Set<AudioMixMatchCriterion> mCriteria;
         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
         private boolean mAllowPrivilegedMediaPlaybackCapture = false;
         // This value should be set internally according to a permission check
@@ -382,7 +389,7 @@
          * Constructs a new Builder with no rules.
          */
         public Builder() {
-            mCriteria = new ArrayList<AudioMixMatchCriterion>();
+            mCriteria = new HashSet<>();
         }
 
         /**
@@ -547,7 +554,12 @@
                 throw new IllegalArgumentException("Illegal argument for mix role");
             }
 
-            Log.i("AudioMixingRule", "Builder setTargetMixRole " + mixRole);
+            if (mCriteria.stream().map(AudioMixMatchCriterion::getRule)
+                    .anyMatch(mixRole == MIX_ROLE_PLAYERS
+                            ? AudioMixingRule::isRecorderRule : AudioMixingRule::isPlayerRule)) {
+                throw new IllegalArgumentException(
+                        "Target mix role is not compatible with mix rules.");
+            }
             mTargetMixType = mixRole == MIX_ROLE_INJECTOR
                     ? AudioMix.MIX_TYPE_RECORDERS : AudioMix.MIX_TYPE_PLAYERS;
             return this;
@@ -604,17 +616,15 @@
          */
         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
                 throws IllegalArgumentException {
-            // as rules are added to the Builder, we verify they are consistent with the type
-            // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+            // If mix type is invalid and added rule is valid only for the players / recorders,
+            // adjust the mix type accordingly.
+            // Otherwise, if the mix type was already deduced or set explicitly, verify the rule
+            // is valid for the mix type.
             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
                 if (isPlayerRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                 } else if (isRecorderRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
-                } else {
-                    // For rules which are not player or recorder specific (e.g. RULE_MATCH_UID),
-                    // the default mix type is MIX_TYPE_PLAYERS.
-                    mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                 }
             } else if ((isPlayerRule(rule) && (mTargetMixType != AudioMix.MIX_TYPE_PLAYERS))
                     || (isRecorderRule(rule)) && (mTargetMixType != AudioMix.MIX_TYPE_RECORDERS))
@@ -622,75 +632,13 @@
                 throw new IllegalArgumentException("Incompatible rule for mix");
             }
             synchronized (mCriteria) {
-                Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
-                final int match_rule = rule & ~RULE_EXCLUSION_MASK;
-                while (crIterator.hasNext()) {
-                    final AudioMixMatchCriterion criterion = crIterator.next();
-
-                    if ((criterion.mRule & ~RULE_EXCLUSION_MASK) != match_rule) {
-                        continue; // The two rules are not of the same type
-                    }
-                    switch (match_rule) {
-                        case RULE_MATCH_ATTRIBUTE_USAGE:
-                            // "usage"-based rule
-                            if (criterion.mAttr.getSystemUsage() == attrToMatch.getSystemUsage()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for " + attrToMatch);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                            // "capture preset"-base rule
-                            if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for " + attrToMatch);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_UID:
-                            // "usage"-based rule
-                            if (criterion.mIntProp == intProp.intValue()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for UID " + intProp);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_USERID:
-                            // "userid"-based rule
-                            if (criterion.mIntProp == intProp.intValue()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for userId " + intProp);
-                                }
-                            }
-                            break;
-                    }
+                int oppositeRule = rule ^ RULE_EXCLUSION_MASK;
+                if (mCriteria.stream().anyMatch(criterion -> criterion.mRule == oppositeRule)) {
+                    throw new IllegalArgumentException("AudioMixingRule cannot contain RULE_MATCH_*"
+                            + " and RULE_EXCLUDE_* for the same dimension.");
                 }
-                // rule didn't exist, add it
-                switch (match_rule) {
+                int ruleWithoutExclusion = rule & ~RULE_EXCLUSION_MASK;
+                switch (ruleWithoutExclusion) {
                     case RULE_MATCH_ATTRIBUTE_USAGE:
                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
@@ -734,8 +682,11 @@
          * @return a new {@link AudioMixingRule} object
          */
         public AudioMixingRule build() {
-            return new AudioMixingRule(mTargetMixType, mCriteria,
-                mAllowPrivilegedMediaPlaybackCapture, mVoiceCommunicationCaptureAllowed);
+            return new AudioMixingRule(
+                    mTargetMixType == AudioMix.MIX_TYPE_INVALID
+                            ? AudioMix.MIX_TYPE_PLAYERS : mTargetMixType,
+                    mCriteria, mAllowPrivilegedMediaPlaybackCapture,
+                    mVoiceCommunicationCaptureAllowed);
         }
     }
 }
diff --git a/media/java/android/media/projection/MediaProjectionGlobal.java b/media/java/android/media/projection/MediaProjectionGlobal.java
deleted file mode 100644
index 4374a05..0000000
--- a/media/java/android/media/projection/MediaProjectionGlobal.java
+++ /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 android.media.projection;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.hardware.display.VirtualDisplayConfig;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.view.Surface;
-
-/**
- * This is a helper for MediaProjection when requests are made from outside an application. This
- * should only be used by processes running as shell as a way to capture recordings without being
- * an application. The requests will fail if coming from any process that's not Shell.
- * @hide
- */
-@SystemApi
-public class MediaProjectionGlobal {
-    private static final Object sLock = new Object();
-    private static MediaProjectionGlobal sInstance;
-
-    /**
-     * @return The instance of {@link MediaProjectionGlobal}
-     */
-    @NonNull
-    public static MediaProjectionGlobal getInstance() {
-        synchronized (sLock) {
-            if (sInstance == null) {
-                final IBinder displayBinder = ServiceManager.getService(Context.DISPLAY_SERVICE);
-                final IBinder packageBinder = ServiceManager.getService("package");
-                if (displayBinder != null && packageBinder != null) {
-                    sInstance = new MediaProjectionGlobal(
-                                    IDisplayManager.Stub.asInterface(displayBinder),
-                                    IPackageManager.Stub.asInterface(packageBinder));
-                }
-            }
-            return sInstance;
-        }
-    }
-
-    private final IDisplayManager mDm;
-    private final IPackageManager mPackageManager;
-
-    private MediaProjectionGlobal(IDisplayManager dm, IPackageManager packageManager) {
-        mDm = dm;
-        mPackageManager = packageManager;
-    }
-
-    /**
-     * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
-     * @param name The name for the virtual display
-     * @param width The initial width for the virtual display
-     * @param height The initial height for the virtual display
-     * @param displayIdToMirror The displayId that will be mirrored into the virtual display.
-     * @return VirtualDisplay that can be used to update properties.
-     */
-    @Nullable
-    public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height,
-            int displayIdToMirror, @Nullable Surface surface) {
-
-        // Density doesn't matter since this virtual display is only used for mirroring.
-        VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
-                height, 1 /* densityDpi */)
-                .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
-                .setDisplayIdToMirror(displayIdToMirror);
-        if (surface != null) {
-            builder.setSurface(surface);
-        }
-        VirtualDisplayConfig virtualDisplayConfig = builder.build();
-
-        String[] packages;
-        try {
-            packages = mPackageManager.getPackagesForUid(Process.myUid());
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-
-        // Just use the first one since it just needs to match the package when looking it up by
-        // calling UID in system server.
-        // The call may come from a rooted device, in that case the requesting uid will be root so
-        // it will not have any package name
-        String packageName = packages == null ? null : packages[0];
-        DisplayManagerGlobal.VirtualDisplayCallback
-                callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
-        int displayId;
-        try {
-            displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null,
-                    packageName);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-        return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig,
-                null, callbackWrapper, displayId);
-    }
-}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 9eacc74..7891ee6 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -333,7 +333,11 @@
     @Override
     public String toString() {
         StringBuilder bob = new StringBuilder("PlaybackState {");
-        bob.append("state=").append(mState);
+        bob.append("state=")
+                .append(getStringForStateInt(mState))
+                .append("(")
+                .append(mState)
+                .append(")");
         bob.append(", position=").append(mPosition);
         bob.append(", buffered position=").append(mBufferedPosition);
         bob.append(", speed=").append(mSpeed);
@@ -533,6 +537,38 @@
         }
     };
 
+    /** Returns a human readable string representation of the given int {@code state} */
+    private static String getStringForStateInt(int state) {
+        switch (state) {
+            case STATE_NONE:
+                return "NONE";
+            case STATE_STOPPED:
+                return "STOPPED";
+            case STATE_PAUSED:
+                return "PAUSED";
+            case STATE_PLAYING:
+                return "PLAYING";
+            case STATE_FAST_FORWARDING:
+                return "FAST_FORWARDING";
+            case STATE_REWINDING:
+                return "REWINDING";
+            case STATE_BUFFERING:
+                return "BUFFERING";
+            case STATE_ERROR:
+                return "ERROR";
+            case STATE_CONNECTING:
+                return "CONNECTING";
+            case STATE_SKIPPING_TO_PREVIOUS:
+                return "SKIPPING_TO_PREVIOUS";
+            case STATE_SKIPPING_TO_NEXT:
+                return "SKIPPING_TO_NEXT";
+            case STATE_SKIPPING_TO_QUEUE_ITEM:
+                return "SKIPPING_TO_QUEUE_ITEM";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
     /**
      * {@link PlaybackState.CustomAction CustomActions} can be used to extend the capabilities of
      * the standard transport controls by exposing app specific actions to
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index ecb1d51..9868a14 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -255,21 +255,21 @@
     jmethodID audioProfileConstructorMethodID =
         env->GetMethodID(audioProfileClazz, "<init>", "(IIIII)V");
 
-    jobjectArray videoCodecs = (jobjectArray)env->NewObjectArray(
-            cp->getVideoCodecs().size(), videoProfileClazz, nullptr);
+    jobjectArray videoCodecs = nullptr;
     {
-        int i = 0;
+        auto isAdvancedCodec = [](const MediaProfiles::VideoCodec *vc) -> bool {
+                                  return ((vc->getBitDepth() != 8
+                                        || vc->getChromaSubsampling() != CHROMA_SUBSAMPLING_YUV_420
+                                        || vc->getHdrFormat() != HDR_FORMAT_NONE));
+                              };
+        std::vector<jobject> codecVector;
         for (const MediaProfiles::VideoCodec *vc : cp->getVideoCodecs()) {
+            if (isAdvancedCodec(vc) && !static_cast<bool>(advanced)) {
+                continue;
+            }
             chroma_subsampling cs = vc->getChromaSubsampling();
             int bitDepth = vc->getBitDepth();
             hdr_format hdr = vc->getHdrFormat();
-
-            bool isAdvanced =
-                (bitDepth != 8 || cs != CHROMA_SUBSAMPLING_YUV_420 || hdr != HDR_FORMAT_NONE);
-            if (static_cast<bool>(advanced) && !isAdvanced) {
-                continue;
-            }
-
             jobject videoCodec = env->NewObject(videoProfileClazz,
                                                 videoProfileConstructorMethodID,
                                                 vc->getCodec(),
@@ -281,10 +281,17 @@
                                                 static_cast<int>(cs),
                                                 bitDepth,
                                                 static_cast<int>(hdr));
-            env->SetObjectArrayElement(videoCodecs, i++, videoCodec);
+
+            codecVector.push_back(videoCodec);
+        }
+        videoCodecs = (jobjectArray)env->NewObjectArray(codecVector.size(),
+                                                        videoProfileClazz, nullptr);
+
+        int i = 0;
+        for (jobject codecObj : codecVector) {
+             env->SetObjectArrayElement(videoCodecs, i++, codecObj);
         }
     }
-
     jobjectArray audioCodecs;
     if (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LIST_START
             && quality <= CAMCORDER_QUALITY_TIME_LAPSE_LIST_END) {
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 7acb8c7..a991a71f 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -74,4 +74,8 @@
     symbol_file: "libamidi.map.txt",
 
     first_version: "29",
+    export_header_libs: [
+        "amidi",
+    ],
+
 }
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 2fe7b16..262f5f1 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -100,16 +100,12 @@
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status,
                 int newState) {
+            Log.d(TAG, "onConnectionStateChange() status: " + status + ", newState: " + newState);
             String intentAction;
             if (newState == BluetoothProfile.STATE_CONNECTED) {
                 Log.d(TAG, "Connected to GATT server.");
                 Log.d(TAG, "Attempting to start service discovery:" +
                         mBluetoothGatt.discoverServices());
-                if (!mBluetoothGatt.requestMtu(MAX_PACKET_SIZE)) {
-                    Log.e(TAG, "request mtu failed");
-                    mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
-                    mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
-                }
             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                 Log.i(TAG, "Disconnected from GATT server.");
                 close();
@@ -118,6 +114,7 @@
 
         @Override
         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            Log.d(TAG, "onServicesDiscovered() status: " +  status);
             if (status == BluetoothGatt.GATT_SUCCESS) {
                 BluetoothGattService service = gatt.getService(MIDI_SERVICE);
                 if (service != null) {
@@ -137,9 +134,14 @@
                         // Specification says to read the characteristic first and then
                         // switch to receiving notifications
                         mBluetoothGatt.readCharacteristic(characteristic);
-                    }
 
-                    openBluetoothDevice(mBluetoothDevice);
+                        // Request higher MTU size
+                        if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+                            Log.e(TAG, "request mtu failed");
+                            mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+                            mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+                        }
+                    }
                 }
             } else {
                 Log.e(TAG, "onServicesDiscovered received: " + status);
@@ -235,13 +237,13 @@
             System.arraycopy(buffer, 0, mCachedBuffer, 0, count);
 
             if (DEBUG) {
-                logByteArray("Sent ", mCharacteristic.getValue(), 0,
-                       mCharacteristic.getValue().length);
+                logByteArray("Sent ", mCachedBuffer, 0, mCachedBuffer.length);
             }
 
-            if (mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
-                    mCharacteristic.getWriteType()) != BluetoothGatt.GATT_SUCCESS) {
-                Log.w(TAG, "could not write characteristic to Bluetooth GATT");
+            int result = mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
+                    mCharacteristic.getWriteType());
+            if (result != BluetoothGatt.GATT_SUCCESS) {
+                Log.w(TAG, "could not write characteristic to Bluetooth GATT. result: " + result);
                 return false;
             }
 
@@ -254,6 +256,10 @@
         mBluetoothDevice = device;
         mService = service;
 
+        // Set a small default packet size in case there is an issue with configuring MTUs.
+        mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+        mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+
         mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
 
         mContext = context;
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 95d1c6c..7963ff2 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -10,15 +10,12 @@
 android_test {
     name: "audiopolicytest",
     srcs: ["**/*.java"],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
     static_libs: [
-        "mockito-target-minus-junit4",
+        "androidx.test.ext.junit",
         "androidx.test.rules",
-        "android-ex-camera2",
-        "testng",
+        "guava",
+        "hamcrest-library",
+        "platform-test-annotations",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
index a7ab828..f696735 100644
--- a/media/tests/AudioPolicyTest/AndroidManifest.xml
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -24,7 +24,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:label="@string/app_name" android:name="AudioPolicyTest"
+        <activity android:label="@string/app_name" android:name="AudioPolicyTestActivity"
                   android:screenOrientation="landscape" android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -33,11 +33,6 @@
         </activity>
     </application>
 
-    <!--instrumentation android:name=".AudioPolicyTestRunner"
-            android:targetPackage="com.android.audiopolicytest"
-            android:label="AudioManager policy oriented integration tests InstrumentationRunner">
-    </instrumentation-->
-
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="com.android.audiopolicytest"
             android:label="AudioManager policy oriented integration tests InstrumentationRunner">
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
index 27cf943..94df40d 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
@@ -16,28 +16,57 @@
 
 package com.android.audiopolicytest;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
+import static com.android.audiopolicytest.AudioVolumeTestUtil.incrementVolumeIndex;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.testng.Assert.assertThrows;
+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 android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
+import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
 import com.google.common.primitives.Ints;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 
-public class AudioManagerTest extends AudioVolumesTestBase {
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioManagerTest {
     private static final String TAG = "AudioManagerTest";
 
+    private AudioManager mAudioManager;
+
+    @Rule
+    public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
+
+    @Before
+    public void setUp() {
+        mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+    }
+
     //-----------------------------------------------------------------
     // Test getAudioProductStrategies and validate strategies
     //-----------------------------------------------------------------
-    public void testGetAndValidateProductStrategies() throws Exception {
+    @Test
+    public void testGetAndValidateProductStrategies() {
         List<AudioProductStrategy> audioProductStrategies =
                 mAudioManager.getAudioProductStrategies();
         assertTrue(audioProductStrategies.size() > 0);
@@ -101,8 +130,8 @@
     //-----------------------------------------------------------------
     // Test getAudioVolumeGroups and validate volume groups
     //-----------------------------------------------------------------
-
-    public void testGetAndValidateVolumeGroups() throws Exception {
+    @Test
+    public void testGetAndValidateVolumeGroups() {
         List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
         assertTrue(audioVolumeGroups.size() > 0);
 
@@ -118,7 +147,7 @@
             // for each volume group attributes, find the matching product strategy and ensure
             // it is linked the considered volume group
             for (final AudioAttributes aa : avgAttributes) {
-                if (aa.equals(sDefaultAttributes)) {
+                if (aa.equals(DEFAULT_ATTRIBUTES)) {
                     // Some volume groups may not have valid attributes, used for internal
                     // volume management like patch/rerouting
                     // so bailing out strategy retrieval from attributes
@@ -180,6 +209,7 @@
     //-----------------------------------------------------------------
     // Test Volume per Attributes setter/getters
     //-----------------------------------------------------------------
+    @Test
     public void testSetGetVolumePerAttributesWithInvalidAttributes() throws Exception {
         AudioAttributes nullAttributes = null;
 
@@ -197,7 +227,8 @@
                         nullAttributes, 0 /*index*/, 0/*flags*/));
     }
 
-    public void testSetGetVolumePerAttributes() throws Exception {
+    @Test
+    public void testSetGetVolumePerAttributes() {
         for (int usage : AudioAttributes.SDK_USAGES) {
             if (usage == AudioAttributes.USAGE_UNKNOWN) {
                 continue;
@@ -248,12 +279,14 @@
     //-----------------------------------------------------------------
     // Test register/unregister VolumeGroupCallback
     //-----------------------------------------------------------------
-    public void testVolumeGroupCallback() throws Exception {
+    @Test
+    public void testVolumeGroupCallback() {
         List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
         assertTrue(audioVolumeGroups.size() > 0);
 
         AudioVolumeGroupCallbackHelper vgCbReceiver = new AudioVolumeGroupCallbackHelper();
-        mAudioManager.registerVolumeGroupCallback(mContext.getMainExecutor(), vgCbReceiver);
+        mAudioManager.registerVolumeGroupCallback(getApplicationContext().getMainExecutor(),
+                vgCbReceiver);
 
         final List<Integer> publicStreams = Ints.asList(AudioManager.getPublicStreamTypes());
         try {
@@ -273,7 +306,7 @@
 
                 // Set the volume per attributes (if valid) and wait the callback
                 for (final AudioAttributes aa : avgAttributes) {
-                    if (aa.equals(sDefaultAttributes)) {
+                    if (aa.equals(DEFAULT_ATTRIBUTES)) {
                         // Some volume groups may not have valid attributes, used for internal
                         // volume management like patch/rerouting
                         // so bailing out strategy retrieval from attributes
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
new file mode 100644
index 0000000..ad7ab97
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+
+import android.media.AudioAttributes;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for AudioPolicy.
+ *
+ * Run with "atest AudioMixingRuleUnitTests".
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioMixingRuleUnitTests {
+    private static final AudioAttributes USAGE_MEDIA_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
+    private static final AudioAttributes CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
+    private static final int TEST_UID = 42;
+    private static final int OTHER_UID = 77;
+
+    @Test
+    public void testConstructValidRule() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                .build();
+
+        // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
+        // since the rules are valid for both MIX_ROLE_PLAYERS & MIX_ROLE_INJECTOR.
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
+                isAudioMixMatchUidCriterion(TEST_UID)));
+    }
+
+    @Test
+    public void testConstructRuleWithConflictingCriteriaFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .addMixRule(RULE_MATCH_UID, TEST_UID)
+                        // Conflicts with previous criterion.
+                        .addMixRule(RULE_EXCLUDE_UID, OTHER_UID)
+                        .build());
+    }
+
+    @Test
+    public void testRuleBuilderDedupsCriteria() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Identical to previous criterion.
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Identical to first criterion.
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .build();
+
+        assertThat(rule.getCriteria(), hasSize(2));
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
+                isAudioMixMatchUidCriterion(TEST_UID)));
+    }
+
+    @Test
+    public void failsWhenAddAttributeRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match attribute usage requires AudioAttributes, not
+                        // just the int enum value of the usage.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenExcludeAttributeRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match attribute usage requires AudioAttributes, not
+                        // just the int enum value of the usage.
+                        .excludeMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenAddIntRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match uid requires Integer not AudioAttributes.
+                        .addMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenExcludeIntRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match uid requires Integer not AudioAttributes.
+                        .excludeMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+    @Test
+    public void injectorMixTypeDeductionWithGenericRuleSucceeds() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                // UID rule can be used both with MIX_ROLE_PLAYERS and MIX_ROLE_INJECTOR.
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Capture preset rule is only valid for injector, MIX_ROLE_INJECTOR should
+                // be deduced.
+                .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                        CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                .build();
+
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUidCriterion(TEST_UID),
+                isAudioMixMatchCapturePresetCriterion(VOICE_RECOGNITION)));
+    }
+
+    @Test
+    public void settingTheMixTypeToIncompatibleInjectorMixFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                        // Capture preset cannot be defined for MIX_ROLE_PLAYERS.
+                        .setTargetMixRole(MIX_ROLE_PLAYERS)
+                        .build());
+    }
+
+    @Test
+    public void addingPlayersOnlyRuleWithInjectorsOnlyRuleFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // MIX_ROLE_PLAYERS only rule.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        // MIX ROLE_INJECTOR only rule.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+
+    private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule = exclude ? RULE_EXCLUDE_UID : RULE_MATCH_UID;
+                return item.getRule() == expectedRule && item.getIntProp() == uid;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with uid %d",
+                                exclude ? "exclude" : "match", uid));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchUidCriterion(int uid) {
+        return isAudioMixUidCriterion(uid, /*exclude=*/ false);
+    }
+
+    private static Matcher isAudioMixCapturePresetCriterion(int audioSource, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule = exclude
+                        ? RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET
+                        : RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
+                AudioAttributes attributes = item.getAudioAttributes();
+                return item.getRule() == expectedRule
+                        && attributes != null && attributes.getCapturePreset() == audioSource;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with capture preset %d",
+                                exclude ? "exclude" : "match", audioSource));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchCapturePresetCriterion(int audioSource) {
+        return isAudioMixCapturePresetCriterion(audioSource, /*exclude=*/ false);
+    }
+
+    private static Matcher isAudioMixUsageCriterion(int usage, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("usage mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule =
+                        exclude ? RULE_EXCLUDE_ATTRIBUTE_USAGE : RULE_MATCH_ATTRIBUTE_USAGE;
+                AudioAttributes attributes = item.getAudioAttributes();
+                return item.getRule() == expectedRule
+                        && attributes != null && attributes.getUsage() == usage;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with usage %d",
+                                exclude ? "exclude" : "match", usage));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchUsageCriterion(int usage) {
+        return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
+    }
+
+
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
similarity index 90%
rename from media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java
rename to media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
index e0c7b22..e31c01a 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
@@ -19,9 +19,9 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-public class AudioPolicyTest extends Activity  {
+public class AudioPolicyTestActivity extends Activity  {
 
-    public AudioPolicyTest() {
+    public AudioPolicyTestActivity() {
     }
 
     /** Called when the activity is first created. */
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
index 0e918d1..b66545a 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
@@ -16,25 +16,43 @@
 
 package com.android.audiopolicytest;
 
+import static com.android.audiopolicytest.AudioVolumeTestUtil.INVALID_ATTRIBUTES;
+
 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 android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
+import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 
-public class AudioProductStrategyTest extends AudioVolumesTestBase {
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioProductStrategyTest {
     private static final String TAG = "AudioProductStrategyTest";
 
+    @Rule
+    public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
+
     //-----------------------------------------------------------------
     // Test getAudioProductStrategies and validate strategies
     //-----------------------------------------------------------------
-    public void testGetProductStrategies() throws Exception {
+    @Test
+    public void testGetProductStrategies() {
         List<AudioProductStrategy> audioProductStrategies =
                 AudioProductStrategy.getAudioProductStrategies();
 
@@ -65,6 +83,7 @@
     //-----------------------------------------------------------------
     // Test stream to/from attributes conversion
     //-----------------------------------------------------------------
+    @Test
     public void testAudioAttributesFromStreamTypes() throws Exception {
         List<AudioProductStrategy> audioProductStrategies =
                 AudioProductStrategy.getAudioProductStrategies();
@@ -81,7 +100,7 @@
             // hosting this stream type; Bailing out the test, just ensure that any request
             // for reciproque API with the unknown attributes would return default stream
             // for volume control, aka STREAM_MUSIC.
-            if (aaFromStreamType.equals(sInvalidAttributes)) {
+            if (aaFromStreamType.equals(INVALID_ATTRIBUTES)) {
                 assertEquals(AudioSystem.STREAM_MUSIC,
                         AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(
                             aaFromStreamType));
@@ -139,7 +158,8 @@
         }
     }
 
-    public void testAudioAttributesToStreamTypes() throws Exception {
+    @Test
+    public void testAudioAttributesToStreamTypes() {
         List<AudioProductStrategy> audioProductStrategies =
                 AudioProductStrategy.getAudioProductStrategies();
 
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
index 221f1f7..82394a2 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
@@ -16,21 +16,48 @@
 
 package com.android.audiopolicytest;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
+import static com.android.audiopolicytest.AudioVolumeTestUtil.incrementVolumeIndex;
+
 import static org.junit.Assert.assertEquals;
-import static org.testng.Assert.assertThrows;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioVolumeGroup;
 import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class AudioVolumeGroupChangeHandlerTest extends AudioVolumesTestBase {
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioVolumeGroupChangeHandlerTest {
     private static final String TAG = "AudioVolumeGroupChangeHandlerTest";
 
-    public void testRegisterInvalidCallback() throws Exception {
+    @Rule
+    public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
+
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() {
+        mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+    }
+
+    @Test
+    public void testRegisterInvalidCallback() {
         final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
                 new AudioVolumeGroupChangeHandler();
 
@@ -42,7 +69,8 @@
         });
     }
 
-    public void testUnregisterInvalidCallback() throws Exception {
+    @Test
+    public void testUnregisterInvalidCallback() {
         final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
                 new AudioVolumeGroupChangeHandler();
 
@@ -58,7 +86,8 @@
         audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
     }
 
-    public void testRegisterUnregisterCallback() throws Exception {
+    @Test
+    public void testRegisterUnregisterCallback() {
         final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
                 new AudioVolumeGroupChangeHandler();
 
@@ -72,7 +101,8 @@
         audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
     }
 
-    public void testCallbackReceived() throws Exception {
+    @Test
+    public void testCallbackReceived() {
         final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
                 new AudioVolumeGroupChangeHandler();
 
@@ -90,7 +120,7 @@
 
                 List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
                 // Set the volume per attributes (if valid) and wait the callback
-                if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) {
+                if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
                     // Some volume groups may not have valid attributes, used for internal
                     // volume management like patch/rerouting
                     // so bailing out strategy retrieval from attributes
@@ -118,7 +148,8 @@
         }
     }
 
-    public void testMultipleCallbackReceived() throws Exception {
+    @Test
+    public void testMultipleCallbackReceived() {
 
         final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
                 new AudioVolumeGroupChangeHandler();
@@ -144,7 +175,7 @@
 
                 List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
                 // Set the volume per attributes (if valid) and wait the callback
-                if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) {
+                if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
                     // Some volume groups may not have valid attributes, used for internal
                     // volume management like patch/rerouting
                     // so bailing out strategy retrieval from attributes
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java
index 84b24b8..1880983 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java
@@ -16,22 +16,51 @@
 
 package com.android.audiopolicytest;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 
-public class AudioVolumeGroupTest extends AudioVolumesTestBase {
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioVolumeGroupTest {
     private static final String TAG = "AudioVolumeGroupTest";
 
+    @Rule
+    public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
+
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() {
+        mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+    }
+
     //-----------------------------------------------------------------
     // Test getAudioVolumeGroups and validate groud id
     //-----------------------------------------------------------------
-    public void testGetVolumeGroupsFromNonServiceCaller() throws Exception {
+    @Test
+    public void testGetVolumeGroupsFromNonServiceCaller() {
         // The transaction behind getAudioVolumeGroups will fail. Check is done at binder level
         // with policy service. Error is not reported, the list is just empty.
         // Request must come from service components
@@ -44,7 +73,8 @@
     //-----------------------------------------------------------------
     // Test getAudioVolumeGroups and validate groud id
     //-----------------------------------------------------------------
-    public void testGetVolumeGroups() throws Exception {
+    @Test
+    public void testGetVolumeGroups() {
         // Through AudioManager, the transaction behind getAudioVolumeGroups will succeed
         final List<AudioVolumeGroup> audioVolumeGroup = mAudioManager.getAudioVolumeGroups();
         assertNotNull(audioVolumeGroup);
@@ -67,7 +97,7 @@
             // for each volume group attributes, find the matching product strategy and ensure
             // it is linked the considered volume group
             for (final AudioAttributes aa : avgAttributes) {
-                if (aa.equals(sDefaultAttributes)) {
+                if (aa.equals(DEFAULT_ATTRIBUTES)) {
                     // Some volume groups may not have valid attributes, used for internal
                     // volume management like patch/rerouting
                     // so bailing out strategy retrieval from attributes
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestUtil.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestUtil.java
new file mode 100644
index 0000000..42b249f
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestUtil.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.audiopolicytest;
+
+import android.media.AudioAttributes;
+import android.media.audiopolicy.AudioProductStrategy;
+
+class AudioVolumeTestUtil {
+    // Default matches the invalid (empty) attributes from native.
+    // The difference is the input source default which is not aligned between native and java
+    public static final AudioAttributes DEFAULT_ATTRIBUTES =
+            AudioProductStrategy.getDefaultAttributes();
+    public static final AudioAttributes INVALID_ATTRIBUTES = new AudioAttributes.Builder().build();
+
+    public static int resetVolumeIndex(int indexMin, int indexMax) {
+        return (indexMax + indexMin) / 2;
+    }
+
+    public static int incrementVolumeIndex(int index, int indexMin, int indexMax) {
+        return (index + 1 > indexMax) ? resetVolumeIndex(indexMin, indexMax) : ++index;
+    }
+
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
similarity index 74%
rename from media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java
rename to media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
index b30ef30..fc3b198 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
@@ -16,35 +16,36 @@
 
 package com.android.audiopolicytest;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
-import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.core.app.ActivityScenario;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.rules.ExternalResource;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-public class AudioVolumesTestBase extends ActivityInstrumentationTestCase2<AudioPolicyTest> {
-    public AudioManager mAudioManager;
-    Context mContext;
+final class AudioVolumesTestRule extends ExternalResource {
+    private AudioManager mAudioManager;
+    private Context mContext;
     private Map<Integer, Integer> mOriginalStreamVolumes = new HashMap<>();
     private Map<Integer, Integer> mOriginalVolumeGroupVolumes = new HashMap<>();
 
-    // Default matches the invalid (empty) attributes from native.
-    // The difference is the input source default which is not aligned between native and java
-    public static final AudioAttributes sDefaultAttributes =
-            AudioProductStrategy.getDefaultAttributes();
-
-    public static final AudioAttributes sInvalidAttributes = new AudioAttributes.Builder().build();
-
-    public AudioVolumesTestBase() {
-        super("com.android.audiopolicytest", AudioPolicyTest.class);
-    }
-
     /**
      * <p>Note: must be called with shell permission (MODIFY_AUDIO_ROUTING)
      */
@@ -56,14 +57,14 @@
                 // like rerouting/patch since these groups are internal to audio policy manager
                 continue;
             }
-            AudioAttributes avgAttributes = sDefaultAttributes;
+            AudioAttributes avgAttributes = DEFAULT_ATTRIBUTES;
             for (final AudioAttributes aa : avg.getAudioAttributes()) {
                 if (!aa.equals(AudioProductStrategy.getDefaultAttributes())) {
                     avgAttributes = aa;
                     break;
                 }
             }
-            if (avgAttributes.equals(sDefaultAttributes)) {
+            if (avgAttributes.equals(DEFAULT_ATTRIBUTES)) {
                 // This shall not happen, however, not purpose of this base class.
                 // so bailing out.
                 continue;
@@ -82,14 +83,14 @@
             for (final AudioVolumeGroup avg : audioVolumeGroups) {
                 if (avg.getId() == e.getKey()) {
                     assertTrue(!avg.getAudioAttributes().isEmpty());
-                    AudioAttributes avgAttributes = sDefaultAttributes;
+                    AudioAttributes avgAttributes = DEFAULT_ATTRIBUTES;
                     for (final AudioAttributes aa : avg.getAudioAttributes()) {
                         if (!aa.equals(AudioProductStrategy.getDefaultAttributes())) {
                             avgAttributes = aa;
                             break;
                         }
                     }
-                    assertTrue(!avgAttributes.equals(sDefaultAttributes));
+                    assertTrue(!avgAttributes.equals(DEFAULT_ATTRIBUTES));
                     mAudioManager.setVolumeIndexForAttributes(
                             avgAttributes, e.getValue(), AudioManager.FLAG_ALLOW_RINGER_MODES);
                 }
@@ -97,11 +98,11 @@
         }
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        ActivityScenario.launch(AudioPolicyTestActivity.class);
 
-        mContext = getActivity();
+        mContext = getApplicationContext();
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
 
         assertEquals(PackageManager.PERMISSION_GRANTED,
@@ -117,10 +118,8 @@
         storeAllVolumes();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
+    @After
+    public void tearDown() throws Exception {
         // Recover the volume and the ringer mode that the test may have overwritten.
         for (Map.Entry<Integer, Integer> e : mOriginalStreamVolumes.entrySet()) {
             mAudioManager.setStreamVolume(e.getKey(), e.getValue(),
@@ -131,11 +130,5 @@
         restoreAllVolumes();
     }
 
-    public static int resetVolumeIndex(int indexMin, int indexMax) {
-        return (indexMax + indexMin) / 2;
-    }
 
-    public static int incrementVolumeIndex(int index, int indexMin, int indexMax) {
-        return (index + 1 > indexMax) ? resetVolumeIndex(indexMin, indexMax) : ++index;
-    }
 }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 32b7a07..8594ba5 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -27,6 +27,9 @@
     symbol_file: "libandroid.map.txt",
     first_version: "9",
     unversioned_until: "current",
+    export_header_libs: [
+        "libandroid_headers",
+    ],
 }
 
 cc_defaults {
diff --git a/native/android/OWNERS b/native/android/OWNERS
index cfe9734..d41652f 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -23,3 +23,6 @@
 per-file native_window_jni.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file surface_control.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file surface_texture.cpp = file:/graphics/java/android/graphics/OWNERS
+
+# Input
+per-file input.cpp = file:/INPUT_OWNERS
diff --git a/native/android/input.cpp b/native/android/input.cpp
index a231d8f..812db0f 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -295,6 +295,8 @@
             return AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE;
         case android::MotionClassification::DEEP_PRESS:
             return AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS;
+        case android::MotionClassification::TWO_FINGER_SWIPE:
+            return AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 }
 
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 9e4d726..21d4d80 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -311,7 +311,7 @@
         auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
 
         for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
-                          previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
+                  previousReleaseFence, transformHint, frameEvents, ignore] : surfaceControlStats) {
             ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
             aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence;
             aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
@@ -671,7 +671,7 @@
 
                 auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
                 for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence,
-                                  previousReleaseFence, transformHint, frameEvents] :
+                              previousReleaseFence, transformHint, frameEvents, ignore] :
                      surfaceControlStats) {
                     ASurfaceControl* aSurfaceControl =
                             reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index 0a5afe4..db54ae3 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,9 @@
         android:layout_width="24dp"
         android:layout_height="24dp"
         android:layout_marginStart="24dp"
-        android:tint="@android:color/system_accent1_600"/>
+        android:tint="@android:color/system_accent1_600"
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <TextView
         android:id="@android:id/text1"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 54916a2..a3d71b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -30,7 +30,8 @@
         android:layout_height="24dp"
         android:layout_marginTop="8dp"
         android:layout_marginEnd="12dp"
-        android:contentDescription="Permission Icon"/>
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 1644110..276127f 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Трансліруйце змесціва праграм з вашага тэлефона"</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="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%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>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 101098c..6b17ffd 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Поточно предаване на приложенията на телефона ви"</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="5977509499890099">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ иска разрешение от името на <xliff:g id="DEVICE_TYPE">%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>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 51b9a89..77fb0bc 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Smartphone-Apps streamen"</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 im Namen deines <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="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="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>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 6b59d0a..11ed3c3 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -23,12 +23,12 @@
     <string name="summary_watch" msgid="3002344206574997652">"Se necesita esta aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
     <string name="permission_apps" msgid="6142133265286656158">"Aplicaciones"</string>
     <string name="permission_apps_summary" msgid="798718816711515431">"Proyecta aplicaciones de tu teléfono"</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 desde tu teléfono"</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="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 desde tu teléfono"</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="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 28aaa87..83d0e02 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -28,7 +28,7 @@
     <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="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
-    <string name="title_computer" msgid="4693714143506569253">"Eman informazio hori telefonotik hartzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
+    <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau atzitzeko 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="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index e52db88..263f3ea61 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -28,7 +28,7 @@
     <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="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="permission_notification" msgid="693762568127741203">"اعلان‌ها"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"می‌تواند همه اعلان‌ها، ازجمله اطلاعاتی مثل مخاطبین، پیام‌ها، و عکس‌ها را بخواند"</string>
@@ -40,7 +40,7 @@
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازه دادن"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازه ندادن"</string>
-    <string name="consent_back" msgid="2560683030046918882">"برگشت"</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_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;این اجازه‌ها می‌تواند شامل دسترسی به «میکروفون»، «دوربین»، و «مکان»، و دیگر اجازه‌های حساس در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; شود.&lt;/p&gt; &lt;p&gt;هروقت بخواهید می‌توانید این اجازه‌ها را در «تنظیمات» در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; تغییر دهید.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"نماد برنامه"</string>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index ccec06c..8134e64 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -28,7 +28,7 @@
     <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="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 desde o teu teléfono"</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="permission_notification" msgid="693762568127741203">"Notificacións"</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>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 2d78b26..c4ca37c 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -31,7 +31,7 @@
     <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="permission_notification" msgid="693762568127741203">"सूचनाएं"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"इससे सभी सूचनाएं देखी जा सकती हैं. इसमें संपर्क, मैसेज, और फ़ोटो जैसी जानकारी शामिल होती है"</string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"इससे सभी सूचनाएं देखी जा सकती हैं. इनमें संपर्क, मैसेज, और फ़ोटो जैसी जानकारी शामिल होती है"</string>
     <string name="permission_storage" msgid="6831099350839392343">"फ़ोटो और मीडिया"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index a2c628ff..59f4f89 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -25,13 +25,13 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Streymdu forritum símans"</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 fyrir straumspilun 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="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="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="permission_notification" msgid="693762568127741203">"Tilkynningar"</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_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
     <string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Þjónusta Google Play"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index 3b2ce6d..a3cd4ee3 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -28,7 +28,7 @@
     <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="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="permission_notification" msgid="693762568127741203">"通知"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index edc7d78..c78affa 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -28,7 +28,7 @@
     <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="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="permission_notification" msgid="693762568127741203">"알림"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"연락처, 메시지, 사진 등의 정보를 포함한 모든 알림을 읽을 수 있습니다."</string>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index bc140a2d3..ead2037 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -25,14 +25,14 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Телефондогу колдонмолорду алып ойнотуу"</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="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%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="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиа"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></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>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 6769b6c..6ef9e5d 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -28,7 +28,7 @@
     <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="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="permission_notification" msgid="693762568127741203">"Известувања"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"може да ги чита сите известувања, вклучително и податоци како контакти, пораки и фотографии"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 2039ee0..9c7cc3c 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -22,7 +22,7 @@
     <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="3002344206574997652">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
     <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"De apps van je telefoon streamen"</string>
+    <string name="permission_apps_summary" msgid="798718816711515431">"Stream de apps van je telefoon"</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>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index 87eba3d..8eabaf8 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -28,10 +28,10 @@
     <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="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
-    <string name="title_computer" msgid="4693714143506569253">"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="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="permission_notification" msgid="693762568127741203">"Notificações"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contratos, mensagens e fotos"</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_storage" msgid="6831099350839392343">"Fotos e multimédia"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Serviços do Google Play"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index 1d87de9..d1f949d 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -17,15 +17,15 @@
 <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>
-    <string name="confirmation_title" msgid="3785000297483688997">"Permiteți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să vă acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="3785000297483688997">"Permite 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>
-    <string name="summary_watch" msgid="3002344206574997652">"Această aplicație este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările dvs. și să vă acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
+    <string name="summary_watch" msgid="3002344206574997652">"Această aplicație este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările și să acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
     <string name="permission_apps" msgid="6142133265286656158">"Aplicații"</string>
     <string name="permission_apps_summary" msgid="798718816711515431">"Să redea în stream aplicațiile telefonului"</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 dvs."</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="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>
@@ -39,10 +39,10 @@
     <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permite"</string>
-    <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string>
+    <string name="consent_no" msgid="2640796915611404382">"Nu permite"</string>
     <string name="consent_back" msgid="2560683030046918882">"Înapoi"</string>
-    <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Oferiți 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="4866838188678457084">"&lt;p&gt;Aici pot fi incluse accesul la microfon, la camera foto, la locație ș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;/p&gt; &lt;p&gt;Puteț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;.&lt;/p&gt;"</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="4866838188678457084">"&lt;p&gt;Aici pot fi incluse accesul la microfon, la camera foto, la locație ș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;/p&gt; &lt;p&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;.&lt;/p&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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index 9537dfdb..ff19fa5 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Streamovať aplikácie telefónu"</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">"<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="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="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>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 9cb5f57..812b4df 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"Tiririsha programu za simu 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">"<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="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="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>
@@ -35,7 +35,7 @@
     <string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Huduma za Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<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="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="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 8a3df03..c318796 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -31,7 +31,7 @@
     <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="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్‌లు"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలరు"</string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలదు"</string>
     <string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play సర్వీసులు"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 0740498..65b2ba5 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -23,12 +23,12 @@
     <string name="summary_watch" msgid="3002344206574997652">"‏آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے اس ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
     <string name="permission_apps" msgid="6142133265286656158">"ایپس"</string>
     <string name="permission_apps_summary" msgid="798718816711515431">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</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="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="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; کرنے کی اجازت دیں"</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="permission_notification" msgid="693762568127741203">"اطلاعات"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 12dd892..ede2369 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -25,7 +25,7 @@
     <string name="permission_apps_summary" msgid="798718816711515431">"串流播放手機應用程式內容"</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="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在為 <xliff:g id="DEVICE_TYPE">%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>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4fb575b..9818ee7 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -45,6 +45,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -81,6 +82,7 @@
  *  A CompanionDevice activity response for showing the available
  *  nearby devices to be associated with.
  */
+@SuppressLint("LongLogTag")
 public class CompanionDeviceActivity extends FragmentActivity implements
         CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener {
     private static final boolean DEBUG = false;
@@ -94,6 +96,7 @@
     private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
     private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+    private static final String EXTRA_FORCE_CANCEL_CONFIRMATION = "cancel_confirmation";
 
     private static final String FRAGMENT_DIALOG_TAG = "fragment_dialog";
 
@@ -162,6 +165,12 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         if (DEBUG) Log.d(TAG, "onCreate()");
+        boolean forceCancelDialog = getIntent().getBooleanExtra("cancel_confirmation", false);
+        // Must handle the force cancel request in onNewIntent.
+        if (forceCancelDialog) {
+            Log.i(TAG, "The confirmation does not exist, skipping the cancel request");
+            finish();
+        }
 
         super.onCreate(savedInstanceState);
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
@@ -195,10 +204,23 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
+        // Force cancels the CDM dialog if this activity receives another intent with
+        // EXTRA_FORCE_CANCEL_CONFIRMATION.
+        boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
+
+        if (forCancelDialog) {
+
+            Log.i(TAG, "Cancelling the user confirmation");
+
+            cancel(false, false);
+            return;
+        }
+
         // Handle another incoming request (while we are not done with the original - mRequest -
         // yet).
         final AssociationRequest request = requireNonNull(
                 intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
+
         if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
 
         // We can only "process" one request at a time.
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 732e734..b6876a4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -29,6 +29,7 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -68,6 +69,7 @@
 /**
  *  A CompanionDevice service response for scanning nearby devices
  */
+@SuppressLint("LongLogTag")
 public class CompanionDeviceDiscoveryService extends Service {
     private static final boolean DEBUG = false;
     private static final String TAG = "CDM_CompanionDeviceDiscoveryService";
@@ -103,7 +105,7 @@
 
     private final Runnable mTimeoutRunnable = this::timeout;
 
-    private boolean mStopAfterFirstMatch;;
+    private boolean mStopAfterFirstMatch;
 
     /**
      * A state enum for devices' discovery.
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
new file mode 100644
index 0000000..51943ff
--- /dev/null
+++ b/packages/CredentialManager/Android.bp
@@ -0,0 +1,35 @@
+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: "CredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+    srcs: ["src/**/*.kt"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.activity_activity-compose",
+        "androidx.appcompat_appcompat",
+        "androidx.compose.material_material",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui",
+        "androidx.compose.ui_ui-tooling",
+        "androidx.core_core-ktx",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "androidx.recyclerview_recyclerview",
+    ],
+
+    platform_apis: true,
+
+    kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
new file mode 100644
index 0000000..586ef86
--- /dev/null
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.credentialmanager">
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+
+    <application
+      android:allowBackup="true"
+      android:dataExtractionRules="@xml/data_extraction_rules"
+      android:fullBackupContent="@xml/backup_rules"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name"
+      android:roundIcon="@mipmap/ic_launcher_round"
+      android:supportsRtl="true"
+      android:theme="@style/Theme.CredentialSelector">
+
+    <activity
+        android:name=".CredentialSelectorActivity"
+        android:exported="true"
+        android:label="@string/app_name"
+        android:launchMode="singleInstance"
+        android:noHistory="true"
+        android:excludeFromRecents="true"
+        android:theme="@style/Theme.CredentialSelector">
+    </activity>
+  </application>
+
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml
rename to packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
diff --git a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml b/packages/CredentialManager/res/drawable/ic_launcher_background.xml
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml
rename to packages/CredentialManager/res/drawable/ic_launcher_background.xml
diff --git a/packages/CredentialManager/res/drawable/ic_passkey.xml b/packages/CredentialManager/res/drawable/ic_passkey.xml
new file mode 100644
index 0000000..041a321
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkey.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="24dp"
+    android:viewportWidth="28"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M27.453,13.253C27.453,14.952 26.424,16.411 24.955,17.041L26.21,18.295L24.839,19.666L26.21,21.037L23.305,23.942L22.012,22.65L22.012,17.156C20.385,16.605 19.213,15.066 19.213,13.253C19.213,10.977 21.058,9.133 23.333,9.133C25.609,9.133 27.453,10.977 27.453,13.253ZM25.47,13.254C25.47,14.434 24.514,15.39 23.334,15.39C22.154,15.39 21.197,14.434 21.197,13.254C21.197,12.074 22.154,11.118 23.334,11.118C24.514,11.118 25.47,12.074 25.47,13.254Z"
+      android:fillColor="#00639B"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M17.85,5.768C17.85,8.953 15.268,11.536 12.083,11.536C8.897,11.536 6.315,8.953 6.315,5.768C6.315,2.582 8.897,0 12.083,0C15.268,0 17.85,2.582 17.85,5.768Z"
+      android:fillColor="#00639B"/>
+  <path
+      android:pathData="M0.547,20.15C0.547,16.32 8.23,14.382 12.083,14.382C13.59,14.382 15.684,14.679 17.674,15.269C18.116,16.454 18.952,17.447 20.022,18.089V23.071H0.547V20.15Z"
+      android:fillColor="#00639B"/>
+</vector>
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
rename to packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml
rename to packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp
rename to packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp
rename to packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp
rename to packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp
rename to packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp
rename to packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp
rename to packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp
rename to packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp
rename to packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp
rename to packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
similarity index 100%
rename from packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp
rename to packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
new file mode 100644
index 0000000..09837df62
--- /dev/null
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="purple_200">#FFBB86FC</color>
+  <color name="purple_500">#FF6200EE</color>
+  <color name="purple_700">#FF3700B3</color>
+  <color name="teal_200">#FF03DAC5</color>
+  <color name="teal_700">#FF018786</color>
+  <color name="black">#FF000000</color>
+  <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
new file mode 100644
index 0000000..2901705
--- /dev/null
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -0,0 +1,13 @@
+<resources>
+  <string name="app_name">CredentialManager</string>
+  <string name="string_cancel">Cancel</string>
+  <string name="string_continue">Continue</string>
+  <string name="string_more_options">More options</string>
+  <string name="string_no_thanks">No thanks</string>
+  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
+  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+  <string name="choose_provider_title">Choose your default provider</string>
+  <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
+  <string name="choose_create_option_title">Create a passkey at</string>
+  <string name="choose_sign_in_title">Use saved sign in</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
new file mode 100644
index 0000000..feec746
--- /dev/null
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+  <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
+    <item name="android:statusBarColor">@color/purple_700</item>
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:windowNoTitle">true</item>
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowIsTranslucent">true</item>
+    <item name="android:colorBackgroundCacheHint">@null</item>
+  </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/backup_rules.xml b/packages/CredentialManager/res/xml/backup_rules.xml
new file mode 100644
index 0000000..9b42d90
--- /dev/null
+++ b/packages/CredentialManager/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+  <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/data_extraction_rules.xml b/packages/CredentialManager/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6c3bb0
--- /dev/null
+++ b/packages/CredentialManager/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+  <cloud-backup>
+    <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+  </cloud-backup>
+  <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
new file mode 100644
index 0000000..5918633
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -0,0 +1,151 @@
+package com.android.credentialmanager
+
+import android.content.Context
+import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.createflow.CreatePasskeyUiState
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
+import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.getflow.GetCredentialUiState
+import com.android.credentialmanager.getflow.GetScreenState
+
+// Consider repo per screen, similar to view model?
+class CredentialManagerRepo(
+  private val context: Context
+) {
+  private fun getCredentialProviderList():
+    List<com.android.credentialmanager.getflow.ProviderInfo> {
+      return listOf(
+        com.android.credentialmanager.getflow.ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Google Password Manager",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          credentialOptions = listOf(
+            CredentialOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@gmail.com",
+              id = "id-1",
+            ),
+            CredentialOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett Work",
+              subtitle = "elisa.beckett.work@google.com",
+              id = "id-2",
+            ),
+          )
+        ),
+        com.android.credentialmanager.getflow.ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Lastpass",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          credentialOptions = listOf(
+            CredentialOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@lastpass.com",
+              id = "id-1",
+            ),
+          )
+        ),
+        com.android.credentialmanager.getflow.ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Dashlane",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          credentialOptions = listOf(
+            CredentialOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@dashlane.com",
+              id = "id-1",
+            ),
+          )
+        ),
+      )
+  }
+
+  private fun createCredentialProviderList(): List<ProviderInfo> {
+    return listOf(
+      ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Google Password Manager",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        createOptions = listOf(
+          CreateOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@gmail.com",
+            id = "id-1",
+          ),
+          CreateOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett Work",
+            subtitle = "elisa.beckett.work@google.com",
+            id = "id-2",
+          ),
+        )
+      ),
+      ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Lastpass",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        createOptions = listOf(
+          CreateOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@lastpass.com",
+            id = "id-1",
+          ),
+        )
+      ),
+      ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Dashlane",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        createOptions = listOf(
+          CreateOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@dashlane.com",
+            id = "id-1",
+          ),
+        )
+      ),
+    )
+  }
+
+  fun getCredentialInitialUiState(): GetCredentialUiState {
+    val providerList = getCredentialProviderList()
+    return GetCredentialUiState(
+      providerList,
+      GetScreenState.CREDENTIAL_SELECTION,
+      providerList.first()
+    )
+  }
+
+  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
+    val providerList = createCredentialProviderList()
+    return CreatePasskeyUiState(
+      providers = providerList,
+      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+    )
+  }
+
+  companion object {
+    lateinit var repo: CredentialManagerRepo
+
+    fun setup(context: Context) {
+      repo = CredentialManagerRepo(context)
+    }
+
+    fun getInstance(): CredentialManagerRepo {
+      return repo
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..5cd6a13
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,52 @@
+package com.android.credentialmanager
+
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.runtime.Composable
+import com.android.credentialmanager.common.DialogType
+import com.android.credentialmanager.createflow.CreatePasskeyScreen
+import com.android.credentialmanager.getflow.GetCredentialScreen
+import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
+
+@ExperimentalMaterialApi
+class CredentialSelectorActivity : ComponentActivity() {
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    CredentialManagerRepo.setup(this)
+    val startDestination = intent.extras?.getString(
+      "start_destination",
+      "CREATE_PASSKEY"
+    ) ?: "CREATE_PASSKEY"
+
+    setContent {
+      CredentialSelectorTheme {
+        CredentialManagerBottomSheet(startDestination)
+      }
+    }
+  }
+
+  @ExperimentalMaterialApi
+  @Composable
+  fun CredentialManagerBottomSheet(operationType: String) {
+    val dialogType = DialogType.toDialogType(operationType)
+    when (dialogType) {
+      DialogType.CREATE_PASSKEY -> {
+        CreatePasskeyScreen(cancelActivity = onCancel)
+      }
+      DialogType.GET_CREDENTIALS -> {
+        GetCredentialScreen(cancelActivity = onCancel)
+      }
+      else -> {
+        Log.w("AccountSelector", "Unknown type, not rendering any UI")
+        this.finish()
+      }
+    }
+  }
+
+  private val onCancel = {
+    this@CredentialSelectorActivity.finish()
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
new file mode 100644
index 0000000..8bb80a1
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
@@ -0,0 +1,18 @@
+package com.android.credentialmanager.common
+
+enum class DialogType {
+  CREATE_PASSKEY,
+  GET_CREDENTIALS,
+  CREATE_PASSWORD,
+  UNKNOWN;
+
+  companion object {
+    fun toDialogType(value: String): DialogType {
+      return try {
+        valueOf(value)
+      } catch (e: IllegalArgumentException) {
+        UNKNOWN
+      }
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
new file mode 100644
index 0000000..5aa1e9b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -0,0 +1,25 @@
+package com.android.credentialmanager.createflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+  val icon: Drawable,
+  val name: String,
+  val appDomainName: String,
+  val credentialTypeIcon: Drawable,
+  val createOptions: List<CreateOptionInfo>,
+)
+
+data class CreateOptionInfo(
+  val icon: Drawable,
+  val title: String,
+  val subtitle: String,
+  val id: String,
+)
+
+/** The name of the current screen. */
+enum class CreateScreenState {
+  PASSKEY_INTRO,
+  PROVIDER_SELECTION,
+  CREATION_OPTION_SELECTION,
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
new file mode 100644
index 0000000..fbec1bc
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -0,0 +1,396 @@
+package com.android.credentialmanager.createflow
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonColors
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
+import com.android.credentialmanager.ui.theme.lightSurface1
+
+@ExperimentalMaterialApi
+@Composable
+fun CreatePasskeyScreen(
+  viewModel: CreatePasskeyViewModel = viewModel(),
+  cancelActivity: () -> Unit,
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      val uiState = viewModel.uiState
+      when (uiState.currentScreenState) {
+        CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+          onConfirm = {viewModel.onConfirmIntro()},
+          onCancel = cancelActivity,
+        )
+        CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
+          providerList = uiState.providers,
+          onCancel = cancelActivity,
+          onProviderSelected = {viewModel.onProviderSelected(it)}
+        )
+        CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
+          providerInfo = uiState.selectedProvider!!,
+          onOptionSelected = {viewModel.onCreateOptionSelected(it)},
+          onCancel = cancelActivity,
+          multiProvider = uiState.providers.size > 1,
+          onMoreOptionSelected = {viewModel.onMoreOptionSelected()}
+        )
+      }
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    if (state.currentValue == ModalBottomSheetValue.Hidden) {
+      cancelActivity()
+    }
+  }
+}
+
+@Composable
+fun ConfirmationCard(
+  onConfirm: () -> Unit,
+  onCancel: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        painter = painterResource(R.drawable.ic_passkey),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = stringResource(R.string.passkey_creation_intro_title),
+        style = Typography.subtitle1,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Text(
+        text = stringResource(R.string.passkey_creation_intro_body),
+        style = Typography.body1,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 48.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.SpaceBetween,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(
+          stringResource(R.string.string_cancel),
+          onclick = onCancel
+        )
+        ConfirmButton(
+          stringResource(R.string.string_continue),
+          onclick = onConfirm
+        )
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderSelectionCard(
+  providerList: List<ProviderInfo>,
+  onProviderSelected: (String) -> Unit,
+  onCancel: () -> Unit
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Text(
+        text = stringResource(R.string.choose_provider_title),
+        style = Typography.subtitle1,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = stringResource(R.string.choose_provider_body),
+        style = Typography.body1,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerList.forEach {
+            item {
+              ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_cancel), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onProviderSelected(providerInfo.name)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+            bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+            // painter = painterResource(R.drawable.ic_passkey),
+            // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Text(
+      text = providerInfo.name,
+      style = Typography.button,
+      modifier = Modifier.padding(vertical = 18.dp)
+    )
+  }
+}
+
+@Composable
+fun CancelButton(text: String, onclick: () -> Unit) {
+  val colors = ButtonDefaults.buttonColors(
+    backgroundColor = lightBackgroundColor
+  )
+  NavigationButton(
+    border = BorderStroke(1.dp, lightSurface1),
+    colors = colors,
+    text = text,
+    onclick = onclick)
+}
+
+@Composable
+fun ConfirmButton(text: String, onclick: () -> Unit) {
+  val colors = ButtonDefaults.buttonColors(
+    backgroundColor = lightColorAccentSecondary
+  )
+  NavigationButton(
+    colors = colors,
+    text = text,
+    onclick = onclick)
+}
+
+@Composable
+fun NavigationButton(
+    border: BorderStroke? = null,
+    colors: ButtonColors,
+    text: String,
+    onclick: () -> Unit
+) {
+  Button(
+    onClick = onclick,
+    shape = Shapes.small,
+    colors = colors,
+    border = border
+  ) {
+    Text(text = text, style = Typography.button)
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreationSelectionCard(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = "${stringResource(R.string.choose_create_option_title)} ${providerInfo.name}",
+        style = Typography.subtitle1,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = providerInfo.appDomainName,
+        style = Typography.body2,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerInfo.createOptions.forEach {
+            item {
+              CreateOptionRow(createOptionInfo = it, onOptionSelected = onOptionSelected)
+            }
+          }
+          if (multiProvider) {
+            item {
+              MoreOptionRow(onSelect = onMoreOptionSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_cancel), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onOptionSelected(createOptionInfo.id)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+            // painter = painterResource(R.drawable.ic_passkey),
+        // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Column() {
+      Text(
+        text = createOptionInfo.title,
+        style = Typography.h6,
+        modifier = Modifier.padding(top = 16.dp)
+      )
+      Text(
+        text = createOptionInfo.subtitle,
+        style = Typography.body2,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth().height(52.dp),
+    onClick = onSelect,
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+      Text(
+        text = stringResource(R.string.string_more_options),
+        style = Typography.h6,
+      )
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
new file mode 100644
index 0000000..e42016d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -0,0 +1,58 @@
+package com.android.credentialmanager.createflow
+
+import android.util.Log
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CredentialManagerRepo
+
+data class CreatePasskeyUiState(
+  val providers: List<ProviderInfo>,
+  val currentScreenState: CreateScreenState,
+  val selectedProvider: ProviderInfo? = null,
+)
+
+class CreatePasskeyViewModel(
+  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+) : ViewModel() {
+
+  var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
+    private set
+
+  fun onConfirmIntro() {
+    if (uiState.providers.size > 1) {
+      uiState = uiState.copy(
+        currentScreenState = CreateScreenState.PROVIDER_SELECTION
+      )
+    } else if (uiState.providers.size == 1){
+      uiState = uiState.copy(
+        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+        selectedProvider = uiState.providers.first()
+      )
+    } else {
+      throw java.lang.IllegalStateException("Empty provider list.")
+    }
+  }
+
+  fun onProviderSelected(providerName: String) {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+      selectedProvider = getProviderInfoByName(providerName)
+    )
+  }
+
+  fun onCreateOptionSelected(createOptionId: String) {
+    Log.d("Account Selector", "Option selected for creation: $createOptionId")
+  }
+
+  fun getProviderInfoByName(providerName: String): ProviderInfo {
+    return uiState.providers.single {
+      it.name.equals(providerName)
+    }
+  }
+
+  fun onMoreOptionSelected() {
+    Log.d("Account Selector", "On more option selected")
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
new file mode 100644
index 0000000..1ca70ed
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -0,0 +1,201 @@
+package com.android.credentialmanager.getflow
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.createflow.CancelButton
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+
+@ExperimentalMaterialApi
+@Composable
+fun GetCredentialScreen(
+  viewModel: GetCredentialViewModel = viewModel(),
+  cancelActivity: () -> Unit,
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      val uiState = viewModel.uiState
+      when (uiState.currentScreenState) {
+        GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
+          providerInfo = uiState.selectedProvider!!,
+          onCancel = cancelActivity,
+          onOptionSelected = {viewModel.onCredentailSelected(it)},
+          multiProvider = uiState.providers.size > 1,
+          onMoreOptionSelected = {viewModel.onMoreOptionSelected()},
+        )
+      }
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    if (state.currentValue == ModalBottomSheetValue.Hidden) {
+      cancelActivity()
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialSelectionCard(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = stringResource(R.string.choose_sign_in_title),
+        style = Typography.subtitle1,
+        modifier = Modifier
+          .padding(all = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = providerInfo.appDomainName,
+        style = Typography.body2,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerInfo.credentialOptions.forEach {
+            item {
+              CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
+            }
+          }
+          if (multiProvider) {
+            item {
+              MoreOptionRow(onSelect = onMoreOptionSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_no_thanks), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialOptionRow(
+    credentialOptionInfo: CredentialOptionInfo,
+    onOptionSelected: (String) -> Unit
+) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onOptionSelected(credentialOptionInfo.id)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+            bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
+        // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Column() {
+      Text(
+        text = credentialOptionInfo.title,
+        style = Typography.h6,
+        modifier = Modifier.padding(top = 16.dp)
+      )
+      Text(
+        text = credentialOptionInfo.subtitle,
+        style = Typography.body2,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth().height(52.dp),
+    onClick = onSelect,
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Text(
+      text = stringResource(R.string.string_more_options),
+      style = Typography.h6,
+    )
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
new file mode 100644
index 0000000..06bcd7f
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -0,0 +1,30 @@
+package com.android.credentialmanager.getflow
+
+import android.util.Log
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CredentialManagerRepo
+
+data class GetCredentialUiState(
+  val providers: List<ProviderInfo>,
+  val currentScreenState: GetScreenState,
+  val selectedProvider: ProviderInfo? = null,
+)
+
+class GetCredentialViewModel(
+  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+) : ViewModel() {
+
+  var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
+      private set
+
+  fun onCredentailSelected(credentialId: String) {
+    Log.d("Account Selector", "credential selected: $credentialId")
+  }
+
+  fun onMoreOptionSelected() {
+    Log.d("Account Selector", "More Option selected")
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
new file mode 100644
index 0000000..867e9c2
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -0,0 +1,23 @@
+package com.android.credentialmanager.getflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+  val icon: Drawable,
+  val name: String,
+  val appDomainName: String,
+  val credentialTypeIcon: Drawable,
+  val credentialOptions: List<CredentialOptionInfo>,
+)
+
+data class CredentialOptionInfo(
+  val icon: Drawable,
+  val title: String,
+  val subtitle: String,
+  val id: String,
+)
+
+/** The name of the current screen. */
+enum class GetScreenState {
+  CREDENTIAL_SELECTION,
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
new file mode 100644
index 0000000..abb4bfb
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
@@ -0,0 +1,14 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Grey100 = Color(0xFFF1F3F4)
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
+val lightColorAccentSecondary = Color(0xFFC2E7FF)
+val lightBackgroundColor = Color(0xFFF0F0F0)
+val lightSurface1 = Color(0xFF6991D6)
+val textColorSecondary = Color(0xFF40484B)
+val textColorPrimary = Color(0xFF191C1D)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
new file mode 100644
index 0000000..cba8658
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+  small = RoundedCornerShape(100.dp),
+  medium = RoundedCornerShape(20.dp),
+  large = RoundedCornerShape(0.dp)
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
new file mode 100644
index 0000000..a9d20ae
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -0,0 +1,47 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+  primary = Purple200,
+  primaryVariant = Purple700,
+  secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+  primary = Purple500,
+  primaryVariant = Purple700,
+  secondary = Teal200
+
+  /* Other default colors to override
+    background = Color.White,
+    surface = Color.White,
+    onPrimary = Color.White,
+    onSecondary = Color.Black,
+    onBackground = Color.Black,
+    onSurface = Color.Black,
+    */
+)
+
+@Composable
+fun CredentialSelectorTheme(
+  darkTheme: Boolean = isSystemInDarkTheme(),
+  content: @Composable () -> Unit
+) {
+  val colors = if (darkTheme) {
+    DarkColorPalette
+  } else {
+    LightColorPalette
+  }
+
+  MaterialTheme(
+    colors = colors,
+    typography = Typography,
+    shapes = Shapes,
+    content = content
+  )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
new file mode 100644
index 0000000..d8fb01c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -0,0 +1,56 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+  subtitle1 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 24.sp,
+    lineHeight = 32.sp,
+  ),
+  body1 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+  ),
+  body2 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+    color = textColorSecondary
+  ),
+  button = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Medium,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+  ),
+  h6 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Medium,
+    fontSize = 16.sp,
+    lineHeight = 24.sp,
+    color = textColorPrimary
+  ),
+
+  /* Other default text styles to override
+    button = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.W500,
+        fontSize = 14.sp
+    ),
+    caption = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 12.sp
+    )
+    */
+)
diff --git a/packages/DynamicSystemInstallationService/res/values-ro/strings.xml b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
index 27b88db..a8a5125 100644
--- a/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
@@ -1,16 +1,16 @@
 <?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="keyguard_description" msgid="8582605799129954556">"Introduceți parola și accesați Actualizările de sistem dinamice"</string>
-    <string name="notification_install_completed" msgid="6252047868415172643">"Sistemul dinamic este pregătit. Ca să începeți să-l folosiți, reporniți dispozitivul."</string>
+    <string name="keyguard_description" msgid="8582605799129954556">"Introdu parola și accesează Actualizările de sistem dinamice"</string>
+    <string name="notification_install_completed" msgid="6252047868415172643">"Sistemul dinamic e pregătit. Ca să începi să-l folosești, repornește dispozitivul."</string>
     <string name="notification_install_inprogress" msgid="7383334330065065017">"Se instalează"</string>
     <string name="notification_install_failed" msgid="4066039210317521404">"Instalarea nu a reușit"</string>
-    <string name="notification_image_validation_failed" msgid="2720357826403917016">"Nu s-a validat imaginea. Abandonați instalarea."</string>
-    <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Rulăm un sistem dinamic. Reporniți pentru a folosi versiunea Android inițială."</string>
-    <string name="notification_action_cancel" msgid="5929299408545961077">"Anulați"</string>
-    <string name="notification_action_discard" msgid="1817481003134947493">"Renunțați"</string>
-    <string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"Reporniți"</string>
-    <string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"Reporniți"</string>
+    <string name="notification_image_validation_failed" msgid="2720357826403917016">"Nu s-a validat imaginea. Abandonează instalarea."</string>
+    <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Rulăm un sistem dinamic. Repornește pentru a folosi versiunea Android inițială."</string>
+    <string name="notification_action_cancel" msgid="5929299408545961077">"Anulează"</string>
+    <string name="notification_action_discard" msgid="1817481003134947493">"Renunță"</string>
+    <string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"Repornește"</string>
+    <string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"Repornește"</string>
     <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"S-a renunțat la sistemul dinamic"</string>
     <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"Nu se poate reporni sau încărca sistemul dinamic"</string>
     <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"Sistemul dinamic nu a fost dezactivat"</string>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index 1f15619..9ca4543 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -24,41 +24,41 @@
     <string name="installing" msgid="4921993079741206516">"Se instalează…"</string>
     <string name="installing_app" msgid="1165095864863849422">"Se instalează <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
     <string name="install_done" msgid="5987363587661783896">"Aplicație instalată."</string>
-    <string name="install_confirm_question" msgid="7663733664476363311">"Doriți să instalați această aplicație?"</string>
-    <string name="install_confirm_question_update" msgid="3348888852318388584">"Doriți să actualizați această aplicație?"</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_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>
-    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Aplicația nu a fost instalată deoarece nu este compatibilă cu tableta dvs."</string>
-    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Aplicația nu este compatibilă cu televizorul dvs."</string>
-    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Aplicația nu a fost instalată deoarece nu este compatibilă cu telefonul dvs."</string>
+    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Aplicația nu a fost instalată deoarece nu este compatibilă cu tableta."</string>
+    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Aplicația nu este compatibilă cu televizorul."</string>
+    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Aplicația nu a fost instalată deoarece nu este compatibilă cu telefonul."</string>
     <string name="install_failed_invalid_apk" msgid="8581007676422623930">"Aplicația nu a fost instalată deoarece pachetul este nevalid."</string>
-    <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe tableta dvs."</string>
-    <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe televizorul dvs."</string>
-    <string name="install_failed_msg" product="default" msgid="6484461562647915707">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe telefonul dvs."</string>
+    <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe tabletă."</string>
+    <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe televizor."</string>
+    <string name="install_failed_msg" product="default" msgid="6484461562647915707">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată pe telefon."</string>
     <string name="launch" msgid="3952550563999890101">"Deschide"</string>
     <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"Administratorul nu permite instalarea aplicațiilor obținute din surse necunoscute"</string>
     <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"Aplicațiile necunoscute nu pot fi instalate de acest utilizator"</string>
     <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"Acest utilizator nu are permisiunea să instaleze aplicații"</string>
     <string name="ok" msgid="7871959885003339302">"OK"</string>
-    <string name="manage_applications" msgid="5400164782453975580">"Gestionați aplicații"</string>
+    <string name="manage_applications" msgid="5400164782453975580">"Gestionează"</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"Spațiu de stocare insuficient"</string>
-    <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată. Eliberați spațiu și încercați din nou."</string>
+    <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi instalată. Eliberează spațiu și încearcă din nou."</string>
     <string name="app_not_found_dlg_title" msgid="5107924008597470285">"Aplicația nu a fost găsită"</string>
     <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Aplicația nu a fost găsită în lista de aplicații instalate."</string>
     <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Nepermis"</string>
     <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"Utilizatorul actual nu are permisiune pentru a face această dezinstalare."</string>
     <string name="generic_error_dlg_title" msgid="5863195085927067752">"Eroare"</string>
     <string name="generic_error_dlg_text" msgid="5287861443265795232">"Aplicația nu a putut fi dezinstalată."</string>
-    <string name="uninstall_application_title" msgid="4045420072401428123">"Dezinstalați aplicația"</string>
-    <string name="uninstall_update_title" msgid="824411791011583031">"Dezinstalați actualizarea"</string>
+    <string name="uninstall_application_title" msgid="4045420072401428123">"Dezinstalează aplicația"</string>
+    <string name="uninstall_update_title" msgid="824411791011583031">"Dezinstalează actualizarea"</string>
     <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> face parte din următoarea aplicație:"</string>
-    <string name="uninstall_application_text" msgid="3816830743706143980">"Doriți să dezinstalați această aplicație?"</string>
-    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Doriți să dezinstalați această aplicație pentru "<b>"toți"</b>" utilizatorii? Aplicația și datele acesteia vor fi eliminate de la "<b>"toți"</b>" utilizatorii de pe acest dispozitiv."</string>
-    <string name="uninstall_application_text_user" msgid="498072714173920526">"Dezinstalați această aplicație pentru utilizatorul <xliff:g id="USERNAME">%1$s</xliff:g>?"</string>
-    <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Doriți să dezinstalați această aplicație din profilul de serviciu?"</string>
-    <string name="uninstall_update_text" msgid="863648314632448705">"Înlocuiți această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate."</string>
-    <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Înlocuiți această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate. Această acțiune va afecta toți utilizatorii dispozitivului, inclusiv pe cei cu profiluri de serviciu."</string>
+    <string name="uninstall_application_text" msgid="3816830743706143980">"Dezinstalezi această aplicație?"</string>
+    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Dezinstalezi această aplicație pentru "<b>"toți"</b>" utilizatorii? Aplicația și datele acesteia vor fi eliminate de la "<b>"toți"</b>" utilizatorii de pe acest dispozitiv."</string>
+    <string name="uninstall_application_text_user" msgid="498072714173920526">"Dezinstalezi această aplicație pentru utilizatorul <xliff:g id="USERNAME">%1$s</xliff:g>?"</string>
+    <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Dezinstalezi această aplicație din profilul de serviciu?"</string>
+    <string name="uninstall_update_text" msgid="863648314632448705">"Înlocuiești această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate."</string>
+    <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Înlocuiești această aplicație cu versiunea din fabrică? Toate datele vor fi eliminate. Această acțiune va afecta toți utilizatorii dispozitivului, inclusiv pe cei cu profiluri de serviciu."</string>
     <string name="uninstall_keep_data" msgid="7002379587465487550">"Păstrează <xliff:g id="SIZE">%1$s</xliff:g> din datele aplicației."</string>
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"Dezinstalări în curs"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Dezinstalări nereușite"</string>
@@ -71,23 +71,23 @@
     <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Nu se poate dezinstala aplicația activă de administrare a dispozitivului"</string>
     <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Nu se poate dezinstala aplicația activă de administrare a dispozitivului pentru <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Aplicația este necesară unor utilizatori sau profiluri și a fost dezinstalată pentru alții"</string>
-    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Aplicația este necesară pentru profilul dvs. și nu poate fi dezinstalată."</string>
+    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Aplicația este necesară pentru profilul tău și nu poate fi dezinstalată."</string>
     <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Aplicația este necesară administratorului dispozitivului și nu poate fi dezinstalată."</string>
-    <string name="manage_device_administrators" msgid="3092696419363842816">"Gestionați aplicațiile de administrare dispozitiv"</string>
-    <string name="manage_users" msgid="1243995386982560813">"Gestionați utilizatorii"</string>
+    <string name="manage_device_administrators" msgid="3092696419363842816">"Gestionează aplicațiile de administrare dispozitiv"</string>
+    <string name="manage_users" msgid="1243995386982560813">"Gestionează utilizatorii"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> nu a putut fi dezinstalată."</string>
     <string name="Parse_error_dlg_text" msgid="1661404001063076789">"A apărut o problemă la analizarea pachetului."</string>
     <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
     <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Acțiunile de instalare și dezinstalare nu sunt acceptate pe Wear."</string>
     <string name="message_staging" msgid="8032722385658438567">"Se pregătește aplicația…"</string>
     <string name="app_name_unknown" msgid="6881210203354323926">"Necunoscut"</string>
-    <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Din motive de securitate, tableta dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string>
-    <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Din motive de securitate, televizorul dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string>
-    <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Din motive de securitate, telefonul dvs. nu are permisiunea să instaleze aplicații necunoscute din această sursă. Puteți modifica această opțiune în Setări."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați această aplicație, acceptați că sunteți singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați aplicația, acceptați că sunteți singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Televizorul și datele dvs. personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalați această aplicație, acceptați că sunteți singura persoană responsabilă pentru deteriorarea televizorului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
-    <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuați"</string>
+    <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Din motive de securitate, tableta nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
+    <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Din motive de securitate, televizorul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
+    <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Din motive de securitate, telefonul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi aplicația, accepți că ești singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Televizorul și datele tale cu caracter personal sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea televizorului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
+    <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuă"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Setări"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Se (dez)instalează aplicațiile Wear"</string>
     <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificare de aplicație instalată"</string>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index e0fb0b8..507088f 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/strings.xml
@@ -27,15 +27,15 @@
     <string name="label_duplex" msgid="5370037254347072243">"Față-verso"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientare"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pagini"</string>
-    <string name="destination_default_text" msgid="5422708056807065710">"Selectați imprimanta"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Selectează imprimanta"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Toate cele <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalul de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"de ex. 1-5, 8, 11-13"</string>
-    <string name="print_preview" msgid="8010217796057763343">"Previzualizați printarea"</string>
-    <string name="install_for_print_preview" msgid="6366303997385509332">"Instalați PDF viewer pentru previzualizare"</string>
+    <string name="print_preview" msgid="8010217796057763343">"Previzualizează printarea"</string>
+    <string name="install_for_print_preview" msgid="6366303997385509332">"Instalează PDF viewer pentru previzualizare"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplicația de printare s-a blocat"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Se generează sarcină printare"</string>
-    <string name="save_as_pdf" msgid="5718454119847596853">"Salvați ca PDF"</string>
+    <string name="save_as_pdf" msgid="5718454119847596853">"Salvează ca PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Toate imprimantele..."</string>
     <string name="print_dialog" msgid="32628687461331979">"Caseta de dialog de printare"</string>
     <string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
@@ -43,18 +43,18 @@
     <string name="summary_template" msgid="8899734908625669193">"Rezumat, copii <xliff:g id="COPIES">%1$s</xliff:g>, dimensiunea paginii <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
     <string name="expand_handle" msgid="7282974448109280522">"Ghidaj de extindere"</string>
     <string name="collapse_handle" msgid="6886637989442507451">"Ghidaj de restrângere"</string>
-    <string name="print_button" msgid="645164566271246268">"Printați"</string>
-    <string name="savetopdf_button" msgid="2976186791686924743">"Salvați în format PDF"</string>
+    <string name="print_button" msgid="645164566271246268">"Printează"</string>
+    <string name="savetopdf_button" msgid="2976186791686924743">"Salvează în format PDF"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"Opțiuni de printare extinse"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"Opțiuni de printare restrânse"</string>
-    <string name="search" msgid="5421724265322228497">"Căutați"</string>
+    <string name="search" msgid="5421724265322228497">"Caută"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Toate imprimantele"</string>
-    <string name="add_print_service_label" msgid="5356702546188981940">"Adăugați un serviciu"</string>
+    <string name="add_print_service_label" msgid="5356702546188981940">"Adaugă un serviciu"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Caseta de căutare este afișată"</string>
     <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Caseta de căutare este ascunsă"</string>
-    <string name="print_add_printer" msgid="1088656468360653455">"Adăugați o imprimantă"</string>
-    <string name="print_select_printer" msgid="7388760939873368698">"Selectați imprimanta"</string>
-    <string name="print_forget_printer" msgid="5035287497291910766">"Omiteți imprimanta"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Adaugă o imprimantă"</string>
+    <string name="print_select_printer" msgid="7388760939873368698">"Selectează imprimanta"</string>
+    <string name="print_forget_printer" msgid="5035287497291910766">"Omite imprimanta"</string>
     <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
       <item quantity="few"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante găsite</item>
       <item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante găsite</item>
@@ -70,26 +70,26 @@
     <string name="print_no_print_services" msgid="8561247706423327966">"Niciun serviciu de printare activat"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nu au fost găsite imprimante"</string>
     <string name="cannot_add_printer" msgid="7840348733668023106">"Nu pot fi adăugate imprimante"</string>
-    <string name="select_to_add_printers" msgid="3800709038689830974">"Selectați pentru a adăuga o imprimantă"</string>
-    <string name="enable_print_service" msgid="3482815747043533842">"Selectați pentru a activa"</string>
+    <string name="select_to_add_printers" msgid="3800709038689830974">"Selectează pentru a adăuga o imprimantă"</string>
+    <string name="enable_print_service" msgid="3482815747043533842">"Selectează pentru a activa"</string>
     <string name="enabled_services_title" msgid="7036986099096582296">"Servicii activate"</string>
     <string name="recommended_services_title" msgid="3799434882937956924">"Servicii recomandate"</string>
     <string name="disabled_services_title" msgid="7313253167968363211">"Servicii dezactivate"</string>
     <string name="all_services_title" msgid="5578662754874906455">"Toate serviciile"</string>
     <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
-      <item quantity="few">Instalați pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item>
-      <item quantity="other">Instalați pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante</item>
-      <item quantity="one">Instalați pentru a descoperi <xliff:g id="COUNT_0">%1$s</xliff:g> imprimantă</item>
+      <item quantity="few">Instalează pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item>
+      <item quantity="other">Instalează pentru a descoperi <xliff:g id="COUNT_1">%1$s</xliff:g> de imprimante</item>
+      <item quantity="one">Instalează pentru a descoperi <xliff:g id="COUNT_0">%1$s</xliff:g> imprimantă</item>
     </plurals>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Se printează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Se anulează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Eroare de printare: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printare blocată: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
-    <string name="cancel" msgid="4373674107267141885">"Anulați"</string>
-    <string name="restart" msgid="2472034227037808749">"Reporniți"</string>
+    <string name="cancel" msgid="4373674107267141885">"Anulează"</string>
+    <string name="restart" msgid="2472034227037808749">"Repornește"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nu există conexiune la o imprimantă"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"necunoscut"</string>
-    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Folosiți <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Folosești <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
     <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Documentul poate trece prin unul sau mai multe servere pe calea spre imprimantă."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Alb-negru"</item>
@@ -105,8 +105,8 @@
     <item msgid="3199660090246166812">"Peisaj"</item>
   </string-array>
     <string name="print_write_error_message" msgid="5787642615179572543">"Nu s-a putut scrie în fișier."</string>
-    <string name="print_error_default_message" msgid="8602678405502922346">"Ne pare rău, operațiunea nu a reușit. Încercați din nou."</string>
-    <string name="print_error_retry" msgid="1426421728784259538">"Reîncercați"</string>
+    <string name="print_error_default_message" msgid="8602678405502922346">"Ne pare rău, operațiunea nu a reușit. Încearcă din nou."</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Reîncearcă"</string>
     <string name="print_error_printer_unavailable" msgid="8985614415253203381">"Această imprimantă nu este disponibilă momentan."</string>
     <string name="print_cannot_load_page" msgid="6179560924492912009">"Previzualizarea nu se poate afișa"</string>
     <string name="print_preparing_preview" msgid="3939930735671364712">"Se pregătește previzualizarea..."</string>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 1df9059..c659525 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -56,8 +56,12 @@
         "SettingsLibDeviceStateRotationLock",
         "setupdesign",
         "zxing-core-1.7",
+        "androidx.room_room-runtime",
+
     ],
 
+    plugins: ["androidx.room_room-compiler-plugin"],
+
     resource_dirs: ["res"],
 
     srcs: [
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-ro/strings.xml b/packages/SettingsLib/BannerMessagePreference/res/values-ro/strings.xml
index 18b6a0e..ff260f5 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-ro/strings.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-ro/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">"Respingeți"</string>
+    <string name="accessibility_banner_message_dismiss" msgid="5272928723898304168">"Închide"</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index ef3db4a..b4b65d4 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "SpaLibTests"
+    },
+    {
+      "name": "SpaPrivilegedLibTests"
     }
   ]
 }
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index f8667ed..6384cad 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -22,7 +22,7 @@
     }
 }
 plugins {
-    id 'com.android.application' version '7.3.0-rc01' apply false
-    id 'com.android.library' version '7.3.0-rc01' apply false
+    id 'com.android.application' version '7.3.0' apply false
+    id 'com.android.library' version '7.3.0' apply false
     id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
index 1ebc5da..332d5a8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
@@ -18,8 +18,4 @@
 
 import com.android.settingslib.spa.framework.DebugActivity
 
-class GalleryDebugActivity : DebugActivity(
-    SpaEnvironment.EntryRepository,
-    browseActivityClass = MainActivity::class.java,
-    entryProviderAuthorities = "com.android.spa.gallery.provider",
-)
+class GalleryDebugActivity : DebugActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
index d3e0096..5e04861 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
@@ -18,7 +18,4 @@
 
 import com.android.settingslib.spa.framework.EntryProvider
 
-class GalleryEntryProvider : EntryProvider(
-    SpaEnvironment.EntryRepository,
-    browseActivityClass = MainActivity::class.java,
-)
+class GalleryEntryProvider : EntryProvider(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
similarity index 88%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 773e5f3..33c4d77 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -18,9 +18,9 @@
 
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -54,7 +54,7 @@
     parameter: List<NamedNavArgument> = emptyList(),
     arguments: Bundle? = null
 ): SettingsPage {
-    return SettingsPage(
+    return SettingsPage.create(
         name = SppName.name,
         displayName = SppName.displayName,
         parameter = parameter,
@@ -62,9 +62,8 @@
     )
 }
 
-object SpaEnvironment {
-    val PageProviderRepository: SettingsPageProviderRepository by
-    lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+object GallerySpaEnvironment : SpaEnvironment() {
+    override val pageProviderRepository = lazy {
         SettingsPageProviderRepository(
             allPageProviders = listOf(
                 HomePageProvider,
@@ -88,9 +87,7 @@
         )
     }
 
-    val EntryRepository: SettingsEntryRepository by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
-        SettingsEntryRepository(PageProviderRepository)
-    }
+    override val browseActivityClass = MainActivity::class.java
 
-    // TODO: add other environment setup here.
+    override val entryProviderAuthorities = "com.android.spa.gallery.provider"
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index a063847..5e859ce 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -18,4 +18,4 @@
 
 import com.android.settingslib.spa.framework.BrowseActivity
 
-class MainActivity : BrowseActivity(SpaEnvironment.PageProviderRepository)
+class MainActivity : BrowseActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index c8a4650..c68e918 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,14 +18,18 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -41,27 +45,36 @@
     override val name = SettingsPageProviderEnum.HOME.name
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = createSettingsPage(SettingsPageProviderEnum.HOME)
         return listOf(
-            PreferenceMainPageProvider.buildInjectEntry().build(),
-            ArgumentPageProvider.buildInjectEntry("foo")!!.build(),
-            SliderPageProvider.buildInjectEntry().build(),
-            SpinnerPageProvider.buildInjectEntry().build(),
-            SettingsPagerPageProvider.buildInjectEntry().build(),
-            FooterPageProvider.buildInjectEntry().build(),
-            IllustrationPageProvider.buildInjectEntry().build(),
-            CategoryPageProvider.buildInjectEntry().build(),
-            ActionButtonPageProvider.buildInjectEntry().build(),
+            PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
+            SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         HomeScaffold(title = stringResource(R.string.app_name)) {
             for (entry in buildEntry(arguments)) {
                 if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
-                    entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
+                    entry.UiLayout(
+                        mergeArguments(
+                            listOf(
+                                globalRuntimeArgs,
+                                ArgumentPageModel.buildArgument(intParam = 0)
+                            )
+                        )
+                    )
                 } else {
-                    entry.UiLayout()
+                    entry.UiLayout(globalRuntimeArgs)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index cc0a79a..9bf7ad8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,12 +18,15 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.widget.preference.Preference
@@ -98,12 +101,20 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
             for (entry in buildEntry(arguments)) {
-                if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
-                    entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
+                if (entry.toPage != null) {
+                    entry.UiLayout(
+                        mergeArguments(
+                            listOf(
+                                globalRuntimeArgs,
+                                ArgumentPageModel.buildNextArgument(arguments)
+                            )
+                        )
+                    )
                 } else {
-                    entry.UiLayout()
+                    entry.UiLayout(globalRuntimeArgs)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index e75d09b..ee2bde4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -22,7 +22,6 @@
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.PageModel
 import com.android.settingslib.spa.framework.compose.navigator
@@ -92,14 +91,12 @@
     private var arguments: Bundle? = null
     private var stringParam: String? = null
     private var intParam: Int? = null
-    private var highlightName: String? = null
 
     override fun initialize(arguments: Bundle?) {
         logMsg("init with args " + arguments.toString())
         this.arguments = arguments
         stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
         intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
-        highlightName = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
     }
 
     @Composable
@@ -133,7 +130,8 @@
             override val title = genPageTitle()
             override val summary = stateOf(summaryArray.joinToString(", "))
             override val onClick = navigator(
-                SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments))
+                SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 9fc736c..e3416c6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
@@ -44,7 +45,7 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.logMsg
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SimplePreferenceMarco
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 
@@ -73,18 +74,18 @@
         entryList.add(
             createEntry(EntryEnum.SIMPLE_PREFERENCE)
                 .setIsAllowSearch(true)
-                .setMarco {
-                    logMsg("create marco for ${EntryEnum.SIMPLE_PREFERENCE}")
-                    SimplePreferenceMarco(title = SIMPLE_PREFERENCE_TITLE)
+                .setMacro {
+                    logMsg("create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
+                    SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
                 .build()
         )
         entryList.add(
             createEntry(EntryEnum.SUMMARY_PREFERENCE)
                 .setIsAllowSearch(true)
-                .setMarco {
-                    logMsg("create marco for ${EntryEnum.SUMMARY_PREFERENCE}")
-                    SimplePreferenceMarco(
+                .setMacro {
+                    logMsg("create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
+                    SimplePreferenceMacro(
                         title = SIMPLE_PREFERENCE_TITLE,
                         summary = SIMPLE_PREFERENCE_SUMMARY,
                         searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
@@ -95,9 +96,9 @@
         entryList.add(
             createEntry(EntryEnum.DISABLED_PREFERENCE)
                 .setIsAllowSearch(true)
-                .setMarco {
-                    logMsg("create marco for ${EntryEnum.DISABLED_PREFERENCE}")
-                    SimplePreferenceMarco(
+                .setMacro {
+                    logMsg("create macro for ${EntryEnum.DISABLED_PREFERENCE}")
+                    SimplePreferenceMacro(
                         title = DISABLE_PREFERENCE_TITLE,
                         summary = DISABLE_PREFERENCE_SUMMARY,
                         disabled = true,
@@ -166,9 +167,9 @@
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = owner)
             .setIsAllowSearch(true)
-            .setMarco {
-                logMsg("create marco for INJECT entry")
-                SimplePreferenceMarco(
+            .setMacro {
+                logMsg("create macro for INJECT entry")
+                SimplePreferenceMacro(
                     title = PAGE_TITLE,
                     clickRoute = SettingsPageProviderEnum.PREFERENCE.name
                 )
@@ -177,9 +178,10 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         RegularScaffold(title = PAGE_TITLE) {
             for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
+                entry.UiLayout(globalRuntimeArgs)
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index 9a525bf..a4713b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -35,7 +35,7 @@
 private const val TITLE = "Sample Category"
 
 object CategoryPageProvider : SettingsPageProvider {
-    override val name = "Spinner"
+    override val name = "Category"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
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 0bb631a..138ea02 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
@@ -31,7 +31,7 @@
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.util.navRoute
@@ -48,9 +48,9 @@
  *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
  *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
  */
-open class BrowseActivity(
-    private val sppRepository: SettingsPageProviderRepository,
-) : ComponentActivity() {
+open class BrowseActivity(spaEnvironment: SpaEnvironment) : ComponentActivity() {
+    private val sppRepository by spaEnvironment.pageProviderRepository
+
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index c4e88e1..85fc366 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -35,8 +35,8 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
 import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
@@ -61,11 +61,9 @@
  * For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity
  * For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity
  */
-open class DebugActivity(
-    private val entryRepository: SettingsEntryRepository,
-    private val browseActivityClass: Class<*>,
-    private val entryProviderAuthorities: String? = null,
-) : ComponentActivity() {
+open class DebugActivity(private val spaEnvironment: SpaEnvironment) : ComponentActivity() {
+    private val entryRepository by spaEnvironment.entryRepository
+
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
@@ -79,7 +77,7 @@
     }
 
     private fun displayDebugMessage() {
-        if (entryProviderAuthorities == null) return
+        val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return
 
         try {
             val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY
@@ -153,7 +151,7 @@
                         "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
                     override val summary = pageWithEntry.page.formatArguments().toState()
                     override val onClick =
-                        navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id()}")
+                        navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
                 })
             }
         }
@@ -172,7 +170,7 @@
         val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
         val pageWithEntry = entryRepository.getPageWithEntry(id)!!
         RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") {
-            Text(text = "id = ${pageWithEntry.page.id()}")
+            Text(text = "id = ${pageWithEntry.page.id}")
             Text(text = pageWithEntry.page.formatArguments())
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
@@ -206,7 +204,7 @@
                 override val title = entry.displayTitle()
                 override val summary =
                     "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
-                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id()}")
+                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
             })
         }
     }
@@ -216,7 +214,7 @@
         if (page.hasRuntimeParam()) return null
         val context = LocalContext.current
         val route = page.buildRoute()
-        val intent = Intent(context, browseActivityClass).apply {
+        val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
             putExtra(KEY_DESTINATION, route)
         }
         return {
@@ -230,7 +228,7 @@
         if (entry.hasRuntimeParam()) return null
         val context = LocalContext.current
         val route = entry.buildRoute()
-        val intent = Intent(context, browseActivityClass).apply {
+        val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
             putExtra(KEY_DESTINATION, route)
         }
         return {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index 33f1eb5..f0ec83b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -28,8 +28,8 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.util.Log
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 
 /**
  * The content provider to return entry related data, which can be used for search and hierarchy.
@@ -38,18 +38,19 @@
  * For gallery, AuthorityPath = com.android.spa.gallery.provider
  * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider
  * Some examples:
- *   $ adb shell content query --uri content://<AuthorityPath>/page_start
+ *   $ adb shell content query --uri content://<AuthorityPath>/page_debug
  *   $ adb shell content query --uri content://<AuthorityPath>/page_info
+ *   $ adb shell content query --uri content://<AuthorityPath>/entry_info
  */
-open class EntryProvider(
-    private val entryRepository: SettingsEntryRepository,
-    private val browseActivityClass: Class<*>? = null,
-) : ContentProvider() {
+open class EntryProvider(spaEnvironment: SpaEnvironment) : ContentProvider() {
+    private val entryRepository by spaEnvironment.entryRepository
+    private val browseActivityClass = spaEnvironment.browseActivityClass
 
     /**
      * Enum to define all column names in provider.
      */
     enum class ColumnEnum(val id: String) {
+        // Columns related to page
         PAGE_ID("pageId"),
         PAGE_NAME("pageName"),
         PAGE_ROUTE("pageRoute"),
@@ -57,6 +58,13 @@
         PAGE_ENTRY_COUNT("entryCount"),
         HAS_RUNTIME_PARAM("hasRuntimeParam"),
         PAGE_START_ADB("pageStartAdb"),
+
+        // Columns related to entry
+        ENTRY_ID("entryId"),
+        ENTRY_NAME("entryName"),
+        ENTRY_ROUTE("entryRoute"),
+        ENTRY_TITLE("entryTitle"),
+        ENTRY_SEARCH_KEYWORD("entrySearchKw"),
     }
 
     /**
@@ -67,12 +75,15 @@
         val queryMatchCode: Int,
         val columnNames: List<ColumnEnum>
     ) {
-        PAGE_START_COMMAND(
-            "page_start", 1,
+        // For debug
+        PAGE_DEBUG_QUERY(
+            "page_debug", 1,
             listOf(ColumnEnum.PAGE_START_ADB)
         ),
+
+        // page related queries.
         PAGE_INFO_QUERY(
-            "page_info", 2,
+            "page_info", 100,
             listOf(
                 ColumnEnum.PAGE_ID,
                 ColumnEnum.PAGE_NAME,
@@ -82,9 +93,24 @@
                 ColumnEnum.HAS_RUNTIME_PARAM,
             )
         ),
+
+        // entry related queries
+        ENTRY_INFO_QUERY(
+            "entry_info", 200,
+            listOf(
+                ColumnEnum.ENTRY_ID,
+                ColumnEnum.ENTRY_NAME,
+                ColumnEnum.ENTRY_ROUTE,
+                ColumnEnum.ENTRY_TITLE,
+                ColumnEnum.ENTRY_SEARCH_KEYWORD,
+            )
+        )
     }
 
-    private var uriMatcher: UriMatcher? = null
+    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+    private fun addUri(authority: String, query: QueryEnum) {
+        uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode)
+    }
 
     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
         TODO("Implement this to handle requests to delete one or more rows")
@@ -115,18 +141,10 @@
     }
 
     override fun attachInfo(context: Context?, info: ProviderInfo?) {
-        uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
         if (info != null) {
-            uriMatcher!!.addURI(
-                info.authority,
-                QueryEnum.PAGE_START_COMMAND.queryPath,
-                QueryEnum.PAGE_START_COMMAND.queryMatchCode
-            )
-            uriMatcher!!.addURI(
-                info.authority,
-                QueryEnum.PAGE_INFO_QUERY.queryPath,
-                QueryEnum.PAGE_INFO_QUERY.queryMatchCode
-            )
+            addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY)
+            addUri(info.authority, QueryEnum.PAGE_INFO_QUERY)
+            addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY)
         }
         super.attachInfo(context, info)
     }
@@ -139,9 +157,10 @@
         sortOrder: String?
     ): Cursor? {
         return try {
-            when (uriMatcher!!.match(uri)) {
-                QueryEnum.PAGE_START_COMMAND.queryMatchCode -> queryPageStartCommand()
+            when (uriMatcher.match(uri)) {
+                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
                 QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
                 else -> throw UnsupportedOperationException("Unknown Uri $uri")
             }
         } catch (e: UnsupportedOperationException) {
@@ -152,8 +171,8 @@
         }
     }
 
-    private fun queryPageStartCommand(): Cursor {
-        val cursor = MatrixCursor(QueryEnum.PAGE_START_COMMAND.getColumns())
+    private fun queryPageDebug(): Cursor {
+        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val command = createBrowsePageAdbCommand(pageWithEntry.page)
             if (command != null) {
@@ -168,7 +187,7 @@
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val page = pageWithEntry.page
             cursor.newRow()
-                .add(ColumnEnum.PAGE_ID.id, page.id())
+                .add(ColumnEnum.PAGE_ID.id, page.id)
                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
                 .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
                 .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
@@ -181,8 +200,26 @@
         return cursor
     }
 
+    private fun queryEntryInfo(): Cursor {
+        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            // We can add runtime arguments if necessary
+            val searchData = entry.getSearchData()
+            cursor.newRow()
+                .add(ColumnEnum.ENTRY_ID.id, entry.id)
+                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+                .add(ColumnEnum.ENTRY_ROUTE.id, entry.buildRoute())
+                .add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "")
+                .add(
+                    ColumnEnum.ENTRY_SEARCH_KEYWORD.id,
+                    searchData?.keyword ?: emptyList<String>()
+                )
+        }
+        return cursor
+    }
+
     private fun createBrowsePageIntent(page: SettingsPage): Intent {
-        if (context == null || browseActivityClass == null || page.hasRuntimeParam())
+        if (context == null || page.hasRuntimeParam())
             return Intent()
 
         return Intent().setComponent(ComponentName(context!!, browseActivityClass)).apply {
@@ -193,8 +230,7 @@
     private fun createBrowsePageAdbCommand(page: SettingsPage): String? {
         if (context == null || page.hasRuntimeParam()) return null
         val packageName = context!!.packageName
-        val activityName =
-            browseActivityClass?.name?.replace(packageName, "") ?: "<browse-activity-class>"
+        val activityName = browseActivityClass.name.replace(packageName, "")
         return "adb shell am start -n $packageName/$activityName" +
             " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
similarity index 80%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
index 8399d12..9ec0c01 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMacro.kt
@@ -19,11 +19,10 @@
 import androidx.compose.runtime.Composable
 
 /**
- * Defines interface of a entry marco, which contains all entry functions to support different
+ * Defines interface of a entry macro, which contains entry functions to support different
  * scenarios, such as browsing (UiLayout), search, etc.
- * SPA team will rebuild some entry marcos, in order to make the entry creation easier.
  */
-interface EntryMarco {
+interface EntryMacro {
     @Composable
     fun UiLayout() {}
     fun getSearchData(): EntrySearchData? = null
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 2239066..81d0bff 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -17,8 +17,13 @@
 package com.android.settingslib.spa.framework.common
 
 import android.os.Bundle
+import android.widget.Toast
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.HIGHLIGHT_ENTRY_PARAM_NAME
 
 const val INJECT_ENTRY_NAME = "INJECT"
 const val ROOT_ENTRY_NAME = "ROOT"
@@ -27,6 +32,9 @@
  * Defines data of a Settings entry.
  */
 data class SettingsEntry(
+    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+    val id: String,
+
     // The name of the page, which is used to compute the unique id, and need to be stable.
     private val name: String,
 
@@ -67,14 +75,9 @@
      */
     private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
 ) {
-    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
-    fun id(): String {
-        return "$name:${owner.id()}(${fromPage?.id()}-${toPage?.id()})".toHashId()
-    }
-
     fun formatContent(): String {
         val content = listOf(
-            "id = ${id()}",
+            "id = $id",
             "owner = ${owner.formatDisplayTitle()}",
             "linkFrom = ${fromPage?.formatDisplayTitle()}",
             "linkTo = ${toPage?.formatDisplayTitle()}",
@@ -94,7 +97,7 @@
     }
 
     fun buildRoute(): String {
-        return containerPage().buildRoute(id())
+        return containerPage().buildRoute(id)
     }
 
     fun hasRuntimeParam(): Boolean {
@@ -104,6 +107,7 @@
     private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
         val arguments = Bundle()
         if (owner.arguments != null) arguments.putAll(owner.arguments)
+        // Put runtime args later, which can override page args.
         if (runtimeArguments != null) arguments.putAll(runtimeArguments)
         return arguments
     }
@@ -114,6 +118,15 @@
 
     @Composable
     fun UiLayout(runtimeArguments: Bundle? = null) {
+        val context = LocalContext.current
+        val highlight = rememberSaveable {
+            mutableStateOf(runtimeArguments?.getString(HIGHLIGHT_ENTRY_PARAM_NAME) == id)
+        }
+        if (highlight.value) {
+            highlight.value = false
+            // TODO: Add highlight entry logic
+            Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
+        }
         uiLayoutImpl(fullArgument(runtimeArguments))
     }
 }
@@ -135,6 +148,7 @@
 
     fun build(): SettingsEntry {
         return SettingsEntry(
+            id = id(),
             name = name,
             owner = owner,
             displayName = displayName,
@@ -171,11 +185,11 @@
         return this
     }
 
-    fun setMarco(fn: (arguments: Bundle?) -> EntryMarco): SettingsEntryBuilder {
+    fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder {
         setSearchDataFn { fn(it).getSearchData() }
         setUiLayoutFn {
-            val marco = remember { fn(it) }
-            marco.UiLayout()
+            val macro = remember { fn(it) }
+            macro.UiLayout()
         }
         return this
     }
@@ -190,6 +204,11 @@
         return this
     }
 
+    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+    private fun id(): String {
+        return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
+    }
+
     companion object {
         fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
             return SettingsEntryBuilder(entryName, owner)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 77f064d..b6f6203 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -44,29 +44,23 @@
         val entryQueue = LinkedList<SettingsEntry>()
         for (page in sppRepository.getAllRootPages()) {
             val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build()
-            val rootEntryId = rootEntry.id()
-            if (!entryMap.containsKey(rootEntryId)) {
+            if (!entryMap.containsKey(rootEntry.id)) {
                 entryQueue.push(rootEntry)
-                entryMap.put(rootEntryId, rootEntry)
+                entryMap.put(rootEntry.id, rootEntry)
             }
         }
 
         while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) {
             val entry = entryQueue.pop()
             val page = entry.toPage
-            val pageId = page?.id()
-            if (pageId == null || pageWithEntryMap.containsKey(pageId)) continue
+            if (page == null || pageWithEntryMap.containsKey(page.id)) continue
             val spp = sppRepository.getProviderOrNull(page.name) ?: continue
-            val newEntries = spp.buildEntry(page.arguments).map {
-                // Set from-page if it is missing.
-                if (it.fromPage == null) it.copy(fromPage = page) else it
-            }
-            pageWithEntryMap[pageId] = SettingsPageWithEntry(page, newEntries)
+            val newEntries = spp.buildEntry(page.arguments)
+            pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
             for (newEntry in newEntries) {
-                val newEntryId = newEntry.id()
-                if (!entryMap.containsKey(newEntryId)) {
+                if (!entryMap.containsKey(newEntry.id)) {
                     entryQueue.push(newEntry)
-                    entryMap.put(newEntryId, newEntry)
+                    entryMap.put(newEntry.id, newEntry)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 124743a..0c301b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -26,6 +26,9 @@
  * Defines data to identify a Settings page.
  */
 data class SettingsPage(
+    // The unique id of this page, which is computed by name + normalized(arguments)
+    val id: String,
+
     // The name of the page, which is used to compute the unique id, and need to be stable.
     val name: String,
 
@@ -41,17 +44,28 @@
     companion object {
         fun create(
             name: String,
+            displayName: String? = null,
             parameter: List<NamedNavArgument> = emptyList(),
             arguments: Bundle? = null
         ): SettingsPage {
-            return SettingsPage(name, name, parameter, arguments)
+            return SettingsPage(
+                id = id(name, parameter, arguments),
+                name = name,
+                displayName = displayName ?: name,
+                parameter = parameter,
+                arguments = arguments
+            )
         }
-    }
 
-    // The unique id of this page, which is computed by name + normalized(arguments)
-    fun id(): String {
-        val normArguments = parameter.normalize(arguments)
-        return "$name:${normArguments?.toString()}".toHashId()
+        // The unique id of this page, which is computed by name + normalized(arguments)
+        private fun id(
+            name: String,
+            parameter: List<NamedNavArgument> = emptyList(),
+            arguments: Bundle? = null
+        ): String {
+            val normArguments = parameter.normalize(arguments)
+            return "$name:${normArguments?.toString()}".toHashId()
+        }
     }
 
     // Returns if this Settings Page is created by the given Spp.
@@ -69,12 +83,12 @@
         return "$displayName ${formatArguments()}"
     }
 
-    fun buildRoute(highlightEntryName: String? = null): String {
+    fun buildRoute(highlightEntryId: String? = null): String {
         val highlightParam =
-            if (highlightEntryName == null)
+            if (highlightEntryId == null)
                 ""
             else
-                "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName"
+                "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryId"
         return name + parameter.navLink(arguments) + highlightParam
     }
 
@@ -114,5 +128,5 @@
 }
 
 fun String.toHashId(): String {
-    return this.hashCode().toString(36)
+    return this.hashCode().toUInt().toString(36)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
similarity index 62%
copy from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt
copy to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 8399d12..111555b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntryMarco.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -16,15 +16,16 @@
 
 package com.android.settingslib.spa.framework.common
 
-import androidx.compose.runtime.Composable
+import android.app.Activity
 
-/**
- * Defines interface of a entry marco, which contains all entry functions to support different
- * scenarios, such as browsing (UiLayout), search, etc.
- * SPA team will rebuild some entry marcos, in order to make the entry creation easier.
- */
-interface EntryMarco {
-    @Composable
-    fun UiLayout() {}
-    fun getSearchData(): EntrySearchData? = null
+abstract class SpaEnvironment {
+    abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository>
+
+    val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+
+    abstract val browseActivityClass: Class<out Activity>
+
+    open val entryProviderAuthorities: String? = null
+
+    // TODO: add other environment setup here.
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index 999d8d7..d801840 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -19,25 +19,33 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.snapshotFlow
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 
+/**
+ * Returns a [Flow] whose values are a list which containing the results of asynchronously applying
+ * the given [transform] function to each element in the original flow's list.
+ */
 inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> =
     map { list -> list.asyncMap(transform) }
 
-@OptIn(ExperimentalCoroutinesApi::class)
-inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> =
-    flatMapLatest { snapshotFlow { block(it).value } }
+/**
+ * Delays the flow a little bit, wait the other flow's first value.
+ */
+fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
+    combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value }
 
-fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> =
-    combine(flow.distinctUntilChangedBy {}) { value, _ -> value }
+/**
+ * Returns a [Flow] whose values are generated list by combining the most recently emitted non null
+ * values by each flow.
+ */
+inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine(
+    flows.asList(),
+) { array: Array<T?> -> array.filterNotNull() }
 
 class StateFlowBridge<T> {
     private val stateFlow = MutableStateFlow<T?>(null)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index d7d7750..452f76a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
+import com.android.settingslib.spa.framework.BrowseActivity
 
 fun List<NamedNavArgument>.navRoute(): String {
     return this.joinToString("") { argument -> "/{${argument.name}}" }
@@ -70,3 +71,21 @@
     }
     return false
 }
+
+fun getRuntimeArguments(arguments: Bundle? = null): Bundle {
+    val res = Bundle()
+    val highlightEntry = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
+    if (highlightEntry != null) {
+        res.putString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME, highlightEntry)
+    }
+    // Append more general runtime arguments here
+    return res
+}
+
+fun mergeArguments(argsList: List<Bundle?>): Bundle {
+    val res = Bundle()
+    for (args in argsList) {
+        if (args != null) res.putAll(args)
+    }
+    return res
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 3555ec7..a9d1c37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.IntrinsicSize
@@ -39,6 +40,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -54,6 +56,7 @@
 data class ActionButton(
     val text: String,
     val imageVector: ImageVector,
+    val enabled: Boolean = true,
     val onClick: () -> Unit,
 )
 
@@ -79,10 +82,15 @@
         modifier = Modifier
             .weight(1f)
             .fillMaxHeight(),
+        enabled = actionButton.enabled,
+        // Because buttons could appear, disappear or change positions, reset the interaction source
+        // to prevent highlight the wrong button.
+        interactionSource = remember(actionButton) { MutableInteractionSource() },
         shape = RectangleShape,
         colors = ButtonDefaults.filledTonalButtonColors(
-            containerColor = MaterialTheme.colorScheme.surface,
+            containerColor = SettingsTheme.colorScheme.surface,
             contentColor = SettingsTheme.colorScheme.categoryTitle,
+            disabledContainerColor = SettingsTheme.colorScheme.surface,
         ),
         contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
     ) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 39b8d57..47abc87 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -22,34 +22,34 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
-import com.android.settingslib.spa.framework.common.EntryMarco
+import com.android.settingslib.spa.framework.common.EntryMacro
 import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.ui.createSettingsIcon
 
-data class SimplePreferenceMarco(
+data class SimplePreferenceMacro(
     val title: String,
     val summary: String? = null,
     val icon: ImageVector? = null,
     val disabled: Boolean = false,
     val clickRoute: String? = null,
     val searchKeywords: List<String> = emptyList(),
-) : EntryMarco {
+) : EntryMacro {
     @Composable
     override fun UiLayout() {
         Preference(model = object : PreferenceModel {
-            override val title: String = this@SimplePreferenceMarco.title
-            override val summary = stateOf(this@SimplePreferenceMarco.summary ?: "")
-            override val icon = createSettingsIcon(this@SimplePreferenceMarco.icon)
-            override val enabled = stateOf(!this@SimplePreferenceMarco.disabled)
+            override val title: String = this@SimplePreferenceMacro.title
+            override val summary = stateOf(this@SimplePreferenceMacro.summary ?: "")
+            override val icon = createSettingsIcon(this@SimplePreferenceMacro.icon)
+            override val enabled = stateOf(!this@SimplePreferenceMacro.disabled)
             override val onClick = navigator(clickRoute)
         })
     }
 
     override fun getSearchData(): EntrySearchData {
         return EntrySearchData(
-            title = this@SimplePreferenceMarco.title,
+            title = this@SimplePreferenceMacro.title,
             keyword = searchKeywords
         )
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index b8e4360..6a88f2d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -16,12 +16,18 @@
 
 package com.android.settingslib.spa.widget.scaffold
 
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.res.stringResource
 import com.android.settingslib.spa.framework.compose.LocalNavController
 
@@ -47,7 +53,21 @@
 }
 
 @Composable
-fun MoreOptionsAction(onClick: () -> Unit) {
+fun MoreOptionsAction(
+    content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit,
+) {
+    var expanded by rememberSaveable { mutableStateOf(false) }
+    MoreOptionsActionButton { expanded = true }
+    val onDismissRequest = { expanded = false }
+    DropdownMenu(
+        expanded = expanded,
+        onDismissRequest = onDismissRequest,
+        content = { content(onDismissRequest) },
+    )
+}
+
+@Composable
+private fun MoreOptionsActionButton(onClick: () -> Unit) {
     IconButton(onClick) {
         Icon(
             imageVector = Icons.Outlined.MoreVert,
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 082ce97..e7e37e4 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -45,3 +45,9 @@
         "-J-Xmx4G",
     ],
 }
+
+// Expose the srcs to tests, so the tests can access the internal classes.
+filegroup {
+    name: "SpaPrivilegedLib_srcs",
+    srcs: ["src/**/*.kt"],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
new file mode 100644
index 0000000..7796549
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -0,0 +1,11 @@
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserManager
+
+/** The [UserManager] instance. */
+val Context.userManager get() = getSystemService(UserManager::class.java)!!
+
+/** The [DevicePolicyManager] instance. */
+val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
new file mode 100644
index 0000000..a7de4ce
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.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 com.android.settingslib.spaprivileged.framework.compose
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+
+/**
+ * A [BroadcastReceiver] which registered when on start and unregistered when on stop.
+ */
+@Composable
+fun DisposableBroadcastReceiverAsUser(
+    userId: Int,
+    intentFilter: IntentFilter,
+    onReceive: (Intent) -> Unit,
+) {
+    val broadcastReceiver = remember {
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                onReceive(intent)
+            }
+        }
+    }
+    val context = LocalContext.current
+    val lifecycleOwner = LocalLifecycleOwner.current
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            if (event == Lifecycle.Event.ON_START) {
+                context.registerReceiverAsUser(
+                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null)
+            } else if (event == Lifecycle.Event.ON_STOP) {
+                context.unregisterReceiver(broadcastReceiver)
+            }
+        }
+
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
similarity index 80%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index bb94b33..ee89003 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,7 +21,6 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.content.pm.UserInfo
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
@@ -30,14 +29,25 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
-class AppsRepository(context: Context) {
+/**
+ * The config used to load the App List.
+ */
+internal data class AppListConfig(
+    val userId: Int,
+    val showInstantApps: Boolean,
+)
+
+/**
+ * The repository to load the App List data.
+ */
+internal class AppListRepository(context: Context) {
     private val packageManager = context.packageManager
 
-    fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow
+    fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
         .map { loadApps(it) }
         .flowOn(Dispatchers.Default)
 
-    private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> {
+    private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
         return coroutineScope {
             val hiddenSystemModulesDeferred = async {
                 packageManager.getInstalledModules(0)
@@ -50,11 +60,11 @@
                     PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
             )
             val installedApplicationsAsUser =
-                packageManager.getInstalledApplicationsAsUser(flags, userInfo.id)
+                packageManager.getInstalledApplicationsAsUser(flags, config.userId)
 
             val hiddenSystemModules = hiddenSystemModulesDeferred.await()
             installedApplicationsAsUser.filter { app ->
-                app.isInAppList(hiddenSystemModules)
+                app.isInAppList(config.showInstantApps, hiddenSystemModules)
             }
         }
     }
@@ -63,9 +73,7 @@
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
     ): Flow<(app: ApplicationInfo) -> Boolean> =
-        userIdFlow.combine(showSystemFlow) { userId, showSystem ->
-            showSystemPredicate(userId, showSystem)
-        }
+        userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
     private suspend fun showSystemPredicate(
         userId: Int,
@@ -102,12 +110,15 @@
     }
 
     companion object {
-        private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) =
-            when {
-                packageName in hiddenSystemModules -> false
-                enabled -> true
-                enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
-                else -> false
-            }
+        private fun ApplicationInfo.isInAppList(
+            showInstantApps: Boolean,
+            hiddenSystemModules: Set<String>,
+        ) = when {
+            !showInstantApps && isInstantApp -> false
+            packageName in hiddenSystemModules -> false
+            enabled -> true
+            isDisabledUntilUsed -> true
+            else -> false
+        }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 9265158..1e487da 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.app.Application
 import android.content.pm.ApplicationInfo
-import android.content.pm.UserInfo
 import android.icu.text.Collator
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
@@ -48,28 +47,29 @@
 internal class AppListViewModel<T : AppRecord>(
     application: Application,
 ) : AndroidViewModel(application) {
-    val userInfo = StateFlowBridge<UserInfo>()
+    val appListConfig = StateFlowBridge<AppListConfig>()
     val listModel = StateFlowBridge<AppListModel<T>>()
     val showSystem = StateFlowBridge<Boolean>()
     val option = StateFlowBridge<Int>()
     val searchQuery = StateFlowBridge<String>()
 
-    private val appsRepository = AppsRepository(application)
+    private val appListRepository = AppListRepository(application)
     private val appRepository = AppRepositoryImpl(application)
     private val collator = Collator.getInstance().freeze()
     private val labelMap = ConcurrentHashMap<String, String>()
     private val scope = viewModelScope + Dispatchers.Default
 
-    private val userIdFlow = userInfo.flow.map { it.id }
+    private val userIdFlow = appListConfig.flow.map { it.userId }
 
     private val recordListFlow = listModel.flow
-        .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) }
+        .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
         .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
-    private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow)
-        .combine(recordListFlow) { showAppPredicate, recordList ->
-            recordList.filter { showAppPredicate(it.app) }
-        }
+    private val systemFilteredFlow =
+        appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+            .combine(recordListFlow) { showAppPredicate, recordList ->
+                recordList.filter { showAppPredicate(it.app) }
+            }
 
     val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
         .combine(searchQuery.flow) { appListData, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
new file mode 100644
index 0000000..c1ac5d4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -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 com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+/** The user id for a given application. */
+val ApplicationInfo.userId: Int
+    get() = UserHandle.getUserId(uid)
+
+/** The [UserHandle] for a given application. */
+val ApplicationInfo.userHandle: UserHandle
+    get() = UserHandle.getUserHandleForUid(uid)
+
+/** Checks whether a flag is associated with the application. */
+fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
+
+/** Checks whether the application is disabled until used. */
+val ApplicationInfo.isDisabledUntilUsed: Boolean
+    get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+
+/** Checks whether the application is disallowed control. */
+fun ApplicationInfo.isDisallowControl(context: Context) =
+    context.userManager.hasBaseUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userHandle)
+
+/** Checks whether the application is an active admin. */
+fun ApplicationInfo.isActiveAdmin(context: Context): Boolean =
+    context.devicePolicyManager.packageHasActiveAdmins(packageName, userId)
+
+/** Converts to the route string which used in navigation. */
+fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index ba8af54..215d22c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -32,9 +32,14 @@
     fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
         getPackageInfoAsUser(packageName, 0, userId)
 
-    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
+    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
         PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
 
+    /** Checks whether a package is installed for a given user. */
+    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+        getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
+            ?: false
+
     fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
         return packageInfo?.requestedPermissions?.let {
@@ -55,7 +60,7 @@
             iPackageManager.isPackageAvailable(it, userId)
         }.toSet()
 
-    private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
         try {
             PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
         } catch (e: PackageManager.NameNotFoundException) {
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 c89ffe5..9611b13 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
@@ -53,15 +53,24 @@
                 ),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
+            val app = packageInfo.applicationInfo
             Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
-                AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
+                AppIcon(app = app, size = SettingsDimension.appIconInfoSize)
             }
-            AppLabel(packageInfo.applicationInfo)
+            AppLabel(app)
+            InstallType(app)
             if (displayVersion) AppVersion()
         }
     }
 
     @Composable
+    private fun InstallType(app: ApplicationInfo) {
+        if (!app.isInstantApp) return
+        Spacer(modifier = Modifier.height(4.dp))
+        SettingsBody(stringResource(R.string.install_type_instant))
+    }
+
+    @Composable
     private fun AppVersion() {
         if (packageInfo.versionName == null) return
         Spacer(modifier = Modifier.height(4.dp))
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 315dc5d..6318b4e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.content.pm.UserInfo
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
@@ -33,6 +32,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppListViewModel
@@ -41,17 +41,22 @@
 
 private const val TAG = "AppList"
 
+/**
+ * The template to render an App List.
+ *
+ * This UI element will take the remaining space on the screen to show the App List.
+ */
 @Composable
 internal fun <T : AppRecord> AppList(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
-    LogCompositions(TAG, userInfo.id.toString())
-    val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery)
+    LogCompositions(TAG, appListConfig.userId.toString())
+    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
     AppListWidget(appListData, listModel, appItem)
 }
 
@@ -85,14 +90,14 @@
 
 @Composable
 private fun <T : AppRecord> loadAppEntries(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString())
-    viewModel.userInfo.setIfAbsent(userInfo)
+    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
+    viewModel.appListConfig.setIfAbsent(appListConfig)
     viewModel.listModel.setIfAbsent(listModel)
     viewModel.showSystem.Sync(showSystem)
     viewModel.option.Sync(option)
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 d537ec2..2be1d1c 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
@@ -20,15 +20,12 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import com.android.settingslib.spa.framework.compose.stateOf
@@ -36,14 +33,19 @@
 import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
 
+/**
+ * The full screen template for an App List page.
+ */
 @Composable
 fun <T : AppRecord> AppListPage(
     title: String,
     listModel: AppListModel<T>,
+    showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
@@ -62,7 +64,10 @@
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    userInfo = userInfo,
+                    appListConfig = AppListConfig(
+                        userId = userInfo.id,
+                        showInstantApps = showInstantApps,
+                    ),
                     listModel = listModel,
                     showSystem = showSystem,
                     option = selectedOption,
@@ -76,17 +81,12 @@
 
 @Composable
 private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) {
-    var expanded by remember { mutableStateOf(false) }
-    MoreOptionsAction { expanded = true }
-    DropdownMenu(
-        expanded = expanded,
-        onDismissRequest = { expanded = false },
-    ) {
+    MoreOptionsAction { onDismissRequest ->
         val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
         DropdownMenuItem(
             text = { Text(stringResource(menuText)) },
             onClick = {
-                expanded = false
+                onDismissRequest()
                 setShowSystem(!showSystem)
             },
         )
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 80b2364..1bbc47d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -63,7 +63,7 @@
     override val parameter = PAGE_PARAMETER
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name, parameter, arguments)
+        val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
@@ -108,7 +108,10 @@
 
         fun buildPageData(permissionType: String): SettingsPage {
             return SettingsPage.create(
-                PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+                name = PAGE_NAME,
+                parameter = PAGE_PARAMETER,
+                arguments = bundleOf(PERMISSION to permissionType)
+            )
         }
     }
 }
@@ -125,7 +128,7 @@
         userId = userId,
         footerText = stringResource(listModel.footerResId),
     ) {
-        val model = createSwitchModel(listModel, packageName, userId)
+        val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
         LaunchedEffect(model, Dispatchers.Default) {
             model.initState()
         }
@@ -138,9 +141,9 @@
     listModel: TogglePermissionAppListModel<T>,
     packageName: String,
     userId: Int,
-): TogglePermissionSwitchModel<T> {
+): TogglePermissionSwitchModel<T>? {
     val record = remember {
-        val app = PackageManagers.getApplicationInfoAsUser(packageName, userId)
+        val app = PackageManagers.getApplicationInfoAsUser(packageName, userId) ?: return null
         listModel.transformItem(app)
     }
     val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index bd60bd36..ec7d75e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -63,7 +63,7 @@
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
-        val appListPage = SettingsPage.create(name, parameter, arguments)
+        val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
         val entryList = mutableListOf<SettingsEntry>()
         // TODO: add more categories, such as personal, work, cloned, etc.
@@ -117,7 +117,10 @@
             listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>,
         ): SettingsEntryBuilder {
             val appListPage = SettingsPage.create(
-                PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+                name = PAGE_NAME,
+                parameter = PAGE_PARAMETER,
+                arguments = bundleOf(PERMISSION to permissionType)
+            )
             return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
                 .setUiLayoutFn {
                     val listModel = rememberContext(listModelSupplier)
diff --git a/packages/SystemUI/compose/gallery/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
similarity index 63%
rename from packages/SystemUI/compose/gallery/tests/Android.bp
rename to packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 3e01f7d..940a1fe 100644
--- a/packages/SystemUI/compose/gallery/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -1,3 +1,4 @@
+//
 // Copyright (C) 2022 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,37 +12,35 @@
 // WITHOUT WARRANTIES 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"],
+    default_applicable_licenses: ["frameworks_base_license"],
 }
 
 android_test {
-    name: "SystemUIComposeGalleryTests",
-    manifest: "AndroidManifest.xml",
-    test_suites: ["device-tests"],
-    sdk_version: "current",
+    name: "SpaPrivilegedLibTests",
     certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
 
     srcs: [
+        ":SpaPrivilegedLib_srcs",
         "src/**/*.kt",
     ],
 
     static_libs: [
-        "SystemUIComposeGalleryLib",
-
-        "androidx.test.runner",
-        "androidx.test.ext.junit",
-
+        "SpaPrivilegedLib",
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
     ],
-
-    kotlincflags: ["-Xjvm-default=enable"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
new file mode 100644
index 0000000..c4f490e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.spaprivileged.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Tests for SpaPrivilegedLib"
+        android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+</manifest>
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
new file mode 100644
index 0000000..c010c68
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.PackageManager.ResolveInfoFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+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.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val USER_ID = 0
+
+@RunWith(AndroidJUnit4::class)
+class AppListRepositoryTest {
+
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var repository: AppListRepository
+
+    private val normalApp = ApplicationInfo().apply {
+        packageName = "normal"
+        enabled = true
+    }
+
+    private val instantApp = ApplicationInfo().apply {
+        packageName = "instant"
+        enabled = true
+        privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+    }
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
+        whenever(
+            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+        ).thenReturn(listOf(normalApp, instantApp))
+        whenever(
+            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+        ).thenReturn(emptyList())
+
+        repository = AppListRepository(context)
+    }
+
+    @Test
+    fun notShowInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp)
+        }
+    }
+
+    @Test
+    fun showInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
+        }
+    }
+}
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 7c4afa7..2bee9fa 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -237,7 +237,7 @@
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"Appareils associés"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Actuellement connecté"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Infos sur l\'appareil"</string>
-    <string name="adb_device_forget" msgid="193072400783068417">"Retirer"</string>
+    <string name="adb_device_forget" msgid="193072400783068417">"Supprimer"</string>
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Empreinte de l\'appareil : <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Échec de la connexion"</string>
     <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Vérifiez que l\'appareil <xliff:g id="DEVICE_NAME">%1$s</xliff:g> est connecté au bon réseau"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 363cd0e..314683b 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -631,7 +631,7 @@
     <string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
     <string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
     <string name="data_connection_carrier_wifi" msgid="8932949159370130465">"W+"</string>
-    <string name="cell_data_off_content_description" msgid="2280700839891636498">"Мобильдік деректер өшірулі"</string>
+    <string name="cell_data_off_content_description" msgid="2280700839891636498">"Мобильдік интернет өшірулі"</string>
     <string name="not_default_data_content_description" msgid="6517068332106592887">"Деректерді пайдалануға реттелмеген."</string>
     <string name="accessibility_no_phone" msgid="2687419663127582503">"Телефон жоқ."</string>
     <string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефон бір баған."</string>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index 987b9c3..faa15c2 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -113,7 +113,7 @@
     <item msgid="8887519571067543785">"96,0 kHz"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
-    <item msgid="2284090879080331090">"Folosiți selectarea sistemului (prestabilit)"</item>
+    <item msgid="2284090879080331090">"Folosește selectarea sistemului (prestabilit)"</item>
     <item msgid="1872276250541651186">"44,1 kHz"</item>
     <item msgid="8736780630001704004">"48,0 kHz"</item>
     <item msgid="7698585706868856888">"88,2 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 8fb09ac..04c58ae 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -133,8 +133,8 @@
     <string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Folosește pentru profilul pentru conținut media audio"</string>
     <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">"Utilizați pentru introducere date"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Folosiți pentru aparatele auditive"</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_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>
@@ -196,7 +196,7 @@
     <string name="tts_status_not_supported" msgid="2702997696245523743">"<xliff:g id="LOCALE">%1$s</xliff:g> nu este acceptată"</string>
     <string name="tts_status_checking" msgid="8026559918948285013">"Se verifică…"</string>
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Setări pentru <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
-    <string name="tts_engine_settings_button" msgid="477155276199968948">"Lansați setările motorului"</string>
+    <string name="tts_engine_settings_button" msgid="477155276199968948">"Lansează setările motorului"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Motor preferat"</string>
     <string name="tts_general_section_title" msgid="8919671529502364567">"Preferințe generale"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Resetează tonalitatea vorbirii"</string>
@@ -279,11 +279,11 @@
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versiunea AVRCP pentru Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selectează versiunea AVRCP pentru Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versiunea MAP pentru Bluetooth"</string>
-    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selectați versiunea MAP pentru Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selectează versiunea MAP pentru Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Declanșează codecul audio Bluetooth\nSelecție"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Rată de eșantionare audio Bluetooth"</string>
-    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Declanșați codecul audio Bluetooth\nSelecție: rată de eșantionare"</string>
+    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Declanșează codecul audio Bluetooth\nSelecție: rată de eșantionare"</string>
     <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"O opțiune inactivă înseamnă incompatibilitate cu telefonul sau setul căști-microfon"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Biți audio Bluetooth per eșantion"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Declanșează codecul audio Bluetooth\nSelecție: biți per eșantion"</string>
@@ -299,7 +299,7 @@
     <string name="private_dns_mode_provider" msgid="3619040641762557028">"Nume de gazdă al furnizorului de DNS privat"</string>
     <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"Introdu numele de gazdă al furnizorului de DNS"</string>
     <string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Nu s-a putut conecta"</string>
-    <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Afișați opțiunile pentru certificarea Ecran wireless"</string>
+    <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Afișează opțiunile pentru certificarea Ecran wireless"</string>
     <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Mărește nivelul de înregistrare prin Wi‑Fi, afișează după SSID RSSI în Selectorul Wi‑Fi"</string>
     <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduce descărcarea bateriei și îmbunătățește performanța rețelei"</string>
     <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"Când acest mod este activat, adresa MAC a dispozitivului se poate schimba de fiecare dată când se conectează la o rețea care are activată randomizarea MAC."</string>
@@ -316,16 +316,16 @@
     <string name="allow_mock_location" msgid="2102650981552527884">"Permite locațiile fictive"</string>
     <string name="allow_mock_location_summary" msgid="179780881081354579">"Permite locațiile fictive"</string>
     <string name="debug_view_attributes" msgid="3539609843984208216">"Activează inspectarea atributelor de vizualizare"</string>
-    <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Păstrați întotdeauna conexiunea de date mobile activată, chiar și atunci când funcția Wi‑Fi este activată (pentru comutarea rapidă între rețele)."</string>
+    <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Păstrează întotdeauna conexiunea de date mobile activată, chiar și atunci când funcția Wi‑Fi este activată (pentru comutarea rapidă între rețele)."</string>
     <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Folosește accelerarea hardware pentru tethering, dacă este disponibilă"</string>
-    <string name="adb_warning_title" msgid="7708653449506485728">"Permiteți remedierea erorilor prin USB?"</string>
-    <string name="adb_warning_message" msgid="8145270656419669221">"Remedierea erorilor prin USB are exclusiv scopuri de dezvoltare. Utilizați-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string>
+    <string name="adb_warning_title" msgid="7708653449506485728">"Permiți remedierea erorilor prin USB?"</string>
+    <string name="adb_warning_message" msgid="8145270656419669221">"Remedierea erorilor prin USB are exclusiv scopuri de dezvoltare. Folosește-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Permiți remedierea erorilor wireless?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"Remedierea erorilor wireless are exclusiv scopuri de dezvoltare. Folosește-o pentru a copia date de pe computer pe dispozitiv, pentru a instala aplicații pe dispozitiv fără notificare și pentru a citi datele din jurnale."</string>
     <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoci accesul la remedierea erorilor prin USB de pe toate computerele pe care le-ai autorizat anterior?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"Permiți setările pentru dezvoltare?"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Aceste setări sunt destinate exclusiv utilizării pentru dezvoltare. Din cauza lor, este posibil ca dispozitivul tău și aplicațiile de pe acesta să nu mai funcționeze sau să funcționeze necorespunzător."</string>
-    <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verificați aplicațiile prin USB"</string>
+    <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verifică aplicațiile prin USB"</string>
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verifică aplicațiile instalate utilizând ADB/ADT, pentru a detecta un comportament dăunător."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Vor fi afișate dispozitivele Bluetooth fără nume (numai adresele MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Dezactivează funcția Bluetooth de volum absolut în cazul problemelor de volum apărute la dispozitivele la distanță, cum ar fi volumul mult prea ridicat sau lipsa de control asupra acestuia."</string>
@@ -383,7 +383,7 @@
     <string name="window_animation_scale_title" msgid="5236381298376812508">"Scară animație fereastră"</string>
     <string name="transition_animation_scale_title" msgid="1278477690695439337">"Scară tranziție animații"</string>
     <string name="animator_duration_scale_title" msgid="7082913931326085176">"Scară durată Animator"</string>
-    <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulați afișaje secundare"</string>
+    <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulează afișaje secundare"</string>
     <string name="debug_applications_category" msgid="5394089406638954196">"Aplicații"</string>
     <string name="immediately_destroy_activities" msgid="1826287490705167403">"Nu păstra activitățile"</string>
     <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Elimină activitățile imediat ce utilizatorul le închide"</string>
@@ -394,10 +394,10 @@
     <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Afișează avertisment pe ecran când o aplicație postează o notificare fără canal valid"</string>
     <string name="force_allow_on_external" msgid="9187902444231637880">"Forțează accesul aplicațiilor la stocarea externă"</string>
     <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Permite scrierea oricărei aplicații eligibile în stocarea externă, indiferent de valorile manifestului"</string>
-    <string name="force_resizable_activities" msgid="7143612144399959606">"Forțați redimensionarea activităților"</string>
-    <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permiteți redimensionarea tuturor activităților pentru modul cu ferestre multiple, indiferent de valorile manifestului."</string>
+    <string name="force_resizable_activities" msgid="7143612144399959606">"Forțează redimensionarea activităților"</string>
+    <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permite redimensionarea tuturor activităților pentru modul cu ferestre multiple, indiferent de valorile manifestului."</string>
     <string name="enable_freeform_support" msgid="7599125687603914253">"Activează ferestrele cu formă liberă"</string>
-    <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activați compatibilitatea pentru ferestrele experimentale cu formă liberă."</string>
+    <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activează compatibilitatea pentru ferestrele experimentale cu formă liberă."</string>
     <string name="desktop_mode" msgid="2389067840550544462">"Modul desktop"</string>
     <string name="local_backup_password_title" msgid="4631017948933578709">"Parolă backup computer"</string>
     <string name="local_backup_password_summary_none" msgid="7646898032616361714">"În prezent, backupurile complete pe computer nu sunt protejate"</string>
@@ -439,7 +439,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corecția culorii poate fi utilă dacă doriți:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;să vedeți mai precis culorile;&lt;/li&gt; &lt;li&gt;&amp;nbsp;să eliminați culorile pentru a vă concentra mai bine.&lt;/li&gt; &lt;/ol&gt;"</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corecția culorii poate fi utilă dacă vrei:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;să vezi mai precis culorile;&lt;/li&gt; &lt;li&gt;&amp;nbsp;să elimini culorile pentru a te concentra mai bine.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -563,10 +563,10 @@
     <string name="user_add_user_title" msgid="5457079143694924885">"Adaugi un utilizator nou?"</string>
     <string name="user_add_user_message_long" msgid="1527434966294733380">"Poți să permiți accesul la acest dispozitiv altor persoane creând utilizatori suplimentari. Fiecare utilizator are propriul spațiu, pe care îl poate personaliza cu aplicații, imagini de fundal etc. De asemenea, utilizatorii pot ajusta setările dispozitivului, cum ar fi setările pentru Wi-Fi, care îi afectează pe toți ceilalți utilizatori.\n\nDupă ce adaugi un utilizator nou, acesta trebuie să-și configureze spațiul.\n\nOricare dintre utilizatori poate actualiza aplicațiile pentru toți ceilalți utilizatori. Este posibil ca setările de accesibilitate și serviciile să nu se transfere la noul utilizator."</string>
     <string name="user_add_user_message_short" msgid="3295959985795716166">"Când adaugi un utilizator nou, acesta trebuie să-și configureze spațiul.\n\nOrice utilizator poate actualiza aplicațiile pentru toți ceilalți utilizatori."</string>
-    <string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurați utilizatorul acum?"</string>
+    <string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurezi utilizatorul acum?"</string>
     <string name="user_setup_dialog_message" msgid="269931619868102841">"Asigură-te că utilizatorul are posibilitatea de a prelua dispozitivul și de a-și configura spațiul"</string>
     <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Configurezi profilul acum?"</string>
-    <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Configurați acum"</string>
+    <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Configurează acum"</string>
     <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Nu acum"</string>
     <string name="user_add_user_type_title" msgid="551279664052914497">"Adaugă"</string>
     <string name="user_new_user_name" msgid="60979820612818840">"Utilizator nou"</string>
@@ -583,7 +583,7 @@
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="user_add_user" msgid="7876449291500212468">"Adaugă un utilizator"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Șterge invitatul"</string>
     <string name="guest_reset_guest" msgid="6110013010356013758">"Resetezi sesiunea pentru invitați"</string>
     <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Resetezi invitatul?"</string>
     <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Excludeți invitatul?"</string>
@@ -594,7 +594,7 @@
     <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Astfel, va începe o nouă sesiune pentru invitați și se vor șterge toate aplicațiile și datele din sesiunea actuală"</string>
     <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Ieși din modul pentru invitați?"</string>
     <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Se vor șterge toate aplicațiile și datele din sesiunea pentru invitați actuală"</string>
-    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ieșiți"</string>
+    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ieși"</string>
     <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Salvezi activitatea invitatului?"</string>
     <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Salvează activitatea din sesiunea actuală sau șterge aplicațiile și datele"</string>
     <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Șterge"</string>
@@ -603,11 +603,11 @@
     <string name="guest_reset_button" msgid="2515069346223503479">"Resetează sesiunea pentru invitați"</string>
     <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Ieși din modul pentru invitați"</string>
     <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toate activitățile vor fi șterse la ieșire"</string>
-    <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puteți să salvați sau să ștergeți activitatea la ieșire"</string>
+    <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Poți să salvezi sau să ștergi activitatea la ieșire"</string>
     <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetează pentru a șterge acum activitatea din sesiune sau salvează ori șterge activitatea la ieșire"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fă o fotografie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Alege o imagine"</string>
-    <string name="user_image_photo_selector" msgid="433658323306627093">"Selectați fotografia"</string>
+    <string name="user_image_photo_selector" msgid="433658323306627093">"Selectează fotografia"</string>
     <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Prea multe încercări incorecte. Datele de pe acest dispozitiv vor fi șterse."</string>
     <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Prea multe încercări incorecte. Acest utilizator va fi șters."</string>
     <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Prea multe încercări incorecte. Acest profil de serviciu și datele sale vor fi șterse."</string>
@@ -652,7 +652,7 @@
     <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"Alege aspectul tastaturii"</string>
     <string name="keyboard_layout_default_label" msgid="1997292217218546957">"Prestabilit"</string>
     <string name="turn_screen_on_title" msgid="3266937298097573424">"Activează ecranul"</string>
-    <string name="allow_turn_screen_on" msgid="6194845766392742639">"Permiteți activarea ecranului"</string>
+    <string name="allow_turn_screen_on" msgid="6194845766392742639">"Permite activarea ecranului"</string>
     <string name="allow_turn_screen_on_description" msgid="43834403291575164">"Permite unei aplicații să activeze ecranul. Dacă acorzi permisiunea, aplicația poate să activeze oricând ecranul, fără intenția ta explicită."</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"Oprești difuzarea <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Dacă difuzezi <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbi rezultatul, difuzarea actuală se va opri"</string>
diff --git a/packages/SettingsLib/res/values-w320dp-port/dimens.xml b/packages/SettingsLib/res/values-w320dp-port/dimens.xml
new file mode 100644
index 0000000..bddf391
--- /dev/null
+++ b/packages/SettingsLib/res/values-w320dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+<resources>
+    <integer name="avatar_picker_columns">2</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index fea7475..5c796af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -237,14 +237,10 @@
      * @return true if it supports advanced metadata, false otherwise.
      */
     public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) {
-        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
-                true)) {
-            Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
+        if (!isAdvancedHeaderEnabled()) {
             return false;
         }
-        // The metadata is for Android R
-        if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
-            Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
+        if (isUntetheredHeadset(bluetoothDevice)) {
             return true;
         }
         // The metadata is for Android S
@@ -260,6 +256,47 @@
     }
 
     /**
+     * Check if the Bluetooth device is supports advanced metadata and an untethered headset
+     *
+     * @param bluetoothDevice the BluetoothDevice to get metadata
+     * @return true if it supports advanced metadata and an untethered headset, false otherwise.
+     */
+    public static boolean isAdvancedUntetheredDevice(@NonNull BluetoothDevice bluetoothDevice) {
+        if (!isAdvancedHeaderEnabled()) {
+            return false;
+        }
+        if (isUntetheredHeadset(bluetoothDevice)) {
+            return true;
+        }
+        // The metadata is for Android S
+        String deviceType = getStringMetaData(bluetoothDevice,
+                BluetoothDevice.METADATA_DEVICE_TYPE);
+        if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
+            Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isAdvancedHeaderEnabled() {
+        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
+                true)) {
+            Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isUntetheredHeadset(@NonNull BluetoothDevice bluetoothDevice) {
+        // The metadata is for Android R
+        if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Create an Icon pointing to a drawable.
      */
     public static IconCompat createIconWithDrawable(Drawable drawable) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7927c5d..eb53ea1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,16 +758,23 @@
     }
 
     public boolean isBusy() {
-        synchronized (mProfileLock) {
-            for (LocalBluetoothProfile profile : mProfiles) {
-                int status = getProfileConnectionState(profile);
-                if (status == BluetoothProfile.STATE_CONNECTING
-                        || status == BluetoothProfile.STATE_DISCONNECTING) {
-                    return true;
-                }
+        for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
+            if (isBusyState(memberDevice)) {
+                return true;
             }
-            return getBondState() == BluetoothDevice.BOND_BONDING;
         }
+        return isBusyState(this);
+    }
+
+    private boolean isBusyState(CachedBluetoothDevice device){
+        for (LocalBluetoothProfile profile : device.getProfiles()) {
+            int status = device.getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTING
+                    || status == BluetoothProfile.STATE_DISCONNECTING) {
+                return true;
+            }
+        }
+        return device.getBondState() == BluetoothDevice.BOND_BONDING;
     }
 
     private boolean updateProfiles() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 123c01b..79fb566 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -210,13 +210,15 @@
 
     LocalBluetoothLeBroadcast(Context context) {
         mExecutor = Executors.newSingleThreadExecutor();
-        BluetoothAdapter.getDefaultAdapter().
-                getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
         mBuilder = new BluetoothLeAudioContentMetadata.Builder();
         mContentResolver = context.getContentResolver();
         Handler handler = new Handler(Looper.getMainLooper());
         mSettingsObserver = new BroadcastSettingsObserver(handler);
         updateBroadcastInfoFromContentProvider();
+
+        // Before registering callback, the constructor should finish creating the all of variables.
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 1be9d76..3903404 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -59,14 +59,14 @@
 
     @Override
     public Drawable getIcon() {
-        return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice())
+        return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
                 ? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
                 : BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first;
     }
 
     @Override
     public Drawable getIconWithoutBackground() {
-        return BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice())
+        return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
                 ? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
                 : BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedDevice).first;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
new file mode 100644
index 0000000..03d9f2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.dataservice;
+
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.UiccCardInfo;
+import android.telephony.UiccPortInfo;
+import android.telephony.UiccSlotInfo;
+import android.telephony.UiccSlotMapping;
+
+public class DataServiceUtils {
+
+    /**
+     * Represents columns of the MobileNetworkInfoData table, define these columns from
+     * {@see MobileNetworkUtils} or relevant common APIs.
+     */
+    public static final class MobileNetworkInfoData {
+
+        /** The name of the MobileNetworkInfoData table. */
+        public static final String TABLE_NAME = "MobileNetworkInfo";
+
+        /**
+         * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()}
+         * as the primary key.
+         */
+        public static final String COLUMN_ID = "subId";
+
+        /**
+         * The name of the contact discovery enabled state column,
+         * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}.
+         */
+        public static final String COLUMN_IS_CONTACT_DISCOVERY_ENABLED =
+                "isContactDiscoveryEnabled";
+
+        /**
+         * The name of the contact discovery visible state column,
+         * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}.
+         */
+        public static final String COLUMN_IS_CONTACT_DISCOVERY_VISIBLE =
+                "isContactDiscoveryVisible";
+
+        /**
+         * The name of the mobile network data state column,
+         * {@see MobileNetworkUtils#isMobileDataEnabled(Context)}.
+         */
+        public static final String COLUMN_IS_MOBILE_DATA_ENABLED = "isMobileDataEnabled";
+
+        /**
+         * The name of the CDMA option state column,
+         * {@see MobileNetworkUtils#isCdmaOptions(Context, int)}.
+         */
+        public static final String COLUMN_IS_CDMA_OPTIONS = "isCdmaOptions";
+
+        /**
+         * The name of the GSM option state column,
+         * {@see MobileNetworkUtils#isGsmOptions(Context, int)}.
+         */
+        public static final String COLUMN_IS_GSM_OPTIONS = "isGsmOptions";
+
+        /**
+         * The name of the world mode state column,
+         * {@see MobileNetworkUtils#isWorldMode(Context, int)}.
+         */
+        public static final String COLUMN_IS_WORLD_MODE = "isWorldMode";
+
+        /**
+         * The name of the display network select options state column,
+         * {@see MobileNetworkUtils#shouldDisplayNetworkSelectOptions(Context, int)}.
+         */
+        public static final String COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS =
+                "shouldDisplayNetworkSelectOptions";
+
+        /**
+         * The name of the TDSCDMA supported state column,
+         * {@see MobileNetworkUtils#isTdscdmaSupported(Context, int)}.
+         */
+        public static final String COLUMN_IS_TDSCDMA_SUPPORTED = "isTdscdmaSupported";
+
+        /**
+         * The name of the active network is cellular state column,
+         * {@see MobileNetworkUtils#activeNetworkIsCellular(Context)}.
+         */
+        public static final String COLUMN_ACTIVE_NETWORK_IS_CELLULAR = "activeNetworkIsCellular";
+
+        /**
+         * The name of the show toggle for physicalSim state column,
+         * {@see SubscriptionUtil#showToggleForPhysicalSim(SubscriptionManager)}.
+         */
+        public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim";
+
+    }
+
+    /**
+     * Represents columns of the UiccInfoData table, define these columns from
+     * {@link android.telephony.UiccSlotInfo}, {@link android.telephony.UiccCardInfo},
+     * {@link UiccSlotMapping} and {@link android.telephony.UiccPortInfo}.If columns of these 4
+     * classes are changed, we should also update the table except PII data.
+     */
+    public static final class UiccInfoData {
+
+        /** The name of the UiccInfoData table. */
+        public static final String TABLE_NAME = "uiccInfo";
+
+        /**
+         * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()}
+         * as the primary key.
+         */
+        public static final String COLUMN_ID = "sudId";
+
+        /**
+         * The name of the physical slot index column, see
+         * {@link UiccSlotMapping#getPhysicalSlotIndex()}.
+         */
+        public static final String COLUMN_PHYSICAL_SLOT_INDEX = "physicalSlotIndex";
+
+        /**
+         * The name of the logical slot index column, see
+         * {@link UiccSlotMapping#getLogicalSlotIndex()}.
+         */
+        public static final String COLUMN_LOGICAL_SLOT_INDEX = "logicalSlotIndex";
+
+        /**
+         * The name of the card ID column, see {@link UiccCardInfo#getCardId()}.
+         */
+        public static final String COLUMN_CARD_ID = "cardId";
+
+        /**
+         * The name of the eUICC state column, see {@link UiccCardInfo#isEuicc()}.
+         */
+        public static final String COLUMN_IS_EUICC = "isEuicc";
+
+        /**
+         * The name of the multiple enabled profiles supported state column, see
+         * {@link UiccCardInfo#isMultipleEnabledProfilesSupported()}.
+         */
+        public static final String COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED =
+                "isMultipleEnabledProfilesSupported";
+
+        /**
+         * The name of the card state column, see {@link UiccSlotInfo#getCardStateInfo()}.
+         */
+        public static final String COLUMN_CARD_STATE = "cardState";
+
+        /**
+         * The name of the extended APDU supported state column, see
+         * {@link UiccSlotInfo#getIsExtendedApduSupported()}.
+         */
+        public static final String COLUMN_IS_EXTENDED_APDU_SUPPORTED = "isExtendedApduSupported";
+
+        /**
+         * The name of the removable state column, see {@link UiccSlotInfo#isRemovable()}.
+         */
+        public static final String COLUMN_IS_REMOVABLE = "isRemovable";
+
+        /**
+         * The name of the active state column, see {@link UiccPortInfo#isActive()}.
+         */
+        public static final String COLUMN_IS_ACTIVE = "isActive";
+
+        /**
+         * The name of the port index column, see {@link UiccPortInfo#getPortIndex()}.
+         */
+        public static final String COLUMN_PORT_INDEX = "portIndex";
+    }
+
+    /**
+     * Represents columns of the SubscriptionInfoData table, define these columns from
+     * {@link SubscriptionInfo}, {@see SubscriptionUtil} and
+     * {@link SubscriptionManager} or relevant common APIs. If columns of the
+     * {@link SubscriptionInfo} are changed, we should also update the table except PII data.
+     */
+    public static final class SubscriptionInfoData {
+
+        /** The name of the SubscriptionInfoData table. */
+        public static final String TABLE_NAME = "subscriptionInfo";
+
+        /**
+         * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()}
+         * as the primary key.
+         */
+        public static final String COLUMN_ID = "sudId";
+
+        /**
+         * The name of the sim slot index column, see
+         * {@link SubscriptionInfo#getSimSlotIndex()}.
+         */
+        public static final String COLUMN_SIM_SLOT_INDEX = "simSlotIndex";
+
+        /**
+         * The name of the carrier ID column, see {@link SubscriptionInfo#getCarrierId()}.
+         */
+        public static final String COLUMN_CARRIER_ID = "carrierId";
+
+        /**
+         * The name of the display name column, see {@link SubscriptionInfo#getDisplayName()}.
+         */
+        public static final String COLUMN_DISPLAY_NAME = "displayName";
+
+        /**
+         * The name of the carrier name column, see {@link SubscriptionInfo#getCarrierName()}.
+         */
+        public static final String COLUMN_CARRIER_NAME = "carrierName";
+
+        /**
+         * The name of the data roaming state column, see
+         * {@link SubscriptionInfo#getDataRoaming()}.
+         */
+        public static final String COLUMN_DATA_ROAMING = "dataRoaming";
+
+        /**
+         * The name of the mcc column, see {@link SubscriptionInfo#getMccString()}.
+         */
+        public static final String COLUMN_MCC = "mcc";
+
+        /**
+         * The name of the mnc column, see {@link SubscriptionInfo#getMncString()}.
+         */
+        public static final String COLUMN_MNC = "mnc";
+
+        /**
+         * The name of the country ISO column, see {@link SubscriptionInfo#getCountryIso()}.
+         */
+        public static final String COLUMN_COUNTRY_ISO = "countryIso";
+
+        /**
+         * The name of the embedded state column, see {@link SubscriptionInfo#isEmbedded()}.
+         */
+        public static final String COLUMN_IS_EMBEDDED = "isEmbedded";
+
+        /**
+         * The name of the card ID column, see {@link SubscriptionInfo#getCardId()}.
+         */
+        public static final String COLUMN_CARD_ID = "cardId";
+
+        /**
+         * The name of the port index column, see {@link SubscriptionInfo#getPortIndex()}.
+         */
+        public static final String COLUMN_PORT_INDEX = "portIndex";
+
+        /**
+         * The name of the opportunistic state column, see
+         * {@link SubscriptionInfo#isOpportunistic()}.
+         */
+        public static final String COLUMN_IS_OPPORTUNISTIC = "isOpportunistic";
+
+        /**
+         * The name of the groupUUID column, see {@link SubscriptionInfo#getGroupUuid()}.
+         */
+        public static final String COLUMN_GROUP_UUID = "groupUUID";
+
+        /**
+         * The name of the subscription type column, see
+         * {@link SubscriptionInfo#getSubscriptionType()}}.
+         */
+        public static final String COLUMN_SUBSCRIPTION_TYPE = "subscriptionType";
+
+        /**
+         * The name of the uniqueName column,
+         * {@see SubscriptionUtil#getUniqueSubscriptionDisplayName(SubscriptionInfo, Context)}.
+         */
+        public static final String COLUMN_UNIQUE_NAME = "uniqueName";
+
+        /**
+         * The name of the subscription visible state column,
+         * {@see SubscriptionUtil#isSubscriptionVisible(SubscriptionManager, Context,
+         * SubscriptionInfo)}.
+         */
+        public static final String COLUMN_IS_SUBSCRIPTION_VISIBLE = "isSubscriptionVisible";
+
+        /**
+         * The name of the formatted phone number column,
+         * {@see SubscriptionUtil#getFormattedPhoneNumber(Context, SubscriptionInfo)}.
+         */
+        public static final String COLUMN_FORMATTED_PHONE_NUMBER = "getFormattedPhoneNumber";
+
+        /**
+         * The name of the first removable subscription state column,
+         * {@see SubscriptionUtil#getFirstRemovableSubscription(Context)}.
+         */
+        public static final String COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION =
+                "isFirstRemovableSubscription";
+
+        /**
+         * The name of the default SIM config column,
+         * {@see SubscriptionUtil#getDefaultSimConfig(Context, int)}.
+         */
+        public static final String COLUMN_DEFAULT_SIM_CONFIG = "defaultSimConfig";
+
+        /**
+         * The name of the default subscription selection column,
+         * {@see SubscriptionUtil#getSubscriptionOrDefault(Context, int)}.
+         */
+        public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION =
+                "isDefaultSubscriptionSelection";
+
+        /**
+         * The name of the valid subscription column,
+         * {@link SubscriptionManager#isValidSubscriptionId(int)}.
+         */
+        public static final String COLUMN_IS_VALID_SUBSCRIPTION = "isValidSubscription";
+
+        /**
+         * The name of the usable subscription column,
+         * {@link SubscriptionManager#isUsableSubscriptionId(int)}.
+         */
+        public static final String COLUMN_IS_USABLE_SUBSCRIPTION = "isUsableSubscription";
+
+        /**
+         * The name of the active subscription column,
+         * {@link SubscriptionManager#isActiveSubscriptionId(int)}.
+         */
+        public static final String COLUMN_IS_ACTIVE_SUBSCRIPTION_ID = "isActiveSubscription";
+
+        /**
+         * The name of the available subscription column,
+         * {@see SubscriptionUtil#getAvailableSubscription(Context, ProxySubscriptionManager, int)}.
+         */
+        public static final String COLUMN_IS_AVAILABLE_SUBSCRIPTION = "isAvailableSubscription";
+
+        /**
+         * The name of the default voice subscription state column, see
+         * {@link SubscriptionManager#getDefaultVoiceSubscriptionId()}.
+         */
+        public static final String COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION =
+                "isDefaultVoiceSubscription";
+
+        /**
+         * The name of the default sms subscription state column, see
+         * {@link SubscriptionManager#getDefaultSmsSubscriptionId()}.
+         */
+        public static final String COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION = "isDefaultSmsSubscription";
+
+        /**
+         * The name of the default data subscription state column, see
+         * {@link SubscriptionManager#getDefaultDataSubscriptionId()}.
+         */
+        public static final String COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION =
+                "isDefaultDataSubscription";
+
+        /**
+         * The name of the default subscription state column, see
+         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+         */
+        public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
new file mode 100644
index 0000000..c1ee7ad
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.dataservice;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.List;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+@Database(entities = {SubscriptionInfoEntity.class, UiccInfoEntity.class,
+        MobileNetworkInfoEntity.class}, exportSchema = false, version = 1)
+public abstract class MobileNetworkDatabase extends RoomDatabase {
+
+    public static final String TAG = "MobileNetworkDatabase";
+
+    public abstract SubscriptionInfoDao mSubscriptionInfoDao();
+
+    public abstract UiccInfoDao mUiccInfoDao();
+
+    public abstract MobileNetworkInfoDao mMobileNetworkInfoDao();
+
+    /**
+     * Create the MobileNetworkDatabase.
+     *
+     * @param context The context.
+     * @return The MobileNetworkDatabase.
+     */
+    public static MobileNetworkDatabase createDatabase(Context context) {
+        return Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class)
+                .fallbackToDestructiveMigration()
+                .enableMultiInstanceInvalidation()
+                .build();
+    }
+
+    /**
+     * Insert the subscription info to the SubscriptionInfoEntity table.
+     *
+     * @param subscriptionInfo The subscriptionInfo.
+     */
+    public void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo) {
+        Log.d(TAG, "insertSubInfo");
+        mSubscriptionInfoDao().insertSubsInfo(subscriptionInfo);
+    }
+
+    /**
+     * Insert the UICC info to the UiccInfoEntity table.
+     *
+     * @param uiccInfoEntity The uiccInfoEntity.
+     */
+    public void insertUiccInfo(UiccInfoEntity... uiccInfoEntity) {
+        Log.d(TAG, "insertUiccInfo");
+        mUiccInfoDao().insertUiccInfo(uiccInfoEntity);
+    }
+
+    /**
+     * Insert the mobileNetwork info to the MobileNetworkInfoEntity table.
+     *
+     * @param mobileNetworkInfoEntity The mobileNetworkInfoEntity.
+     */
+    public void insertMobileNetworkInfo(MobileNetworkInfoEntity... mobileNetworkInfoEntity) {
+        Log.d(TAG, "insertMobileNetworkInfo");
+        mMobileNetworkInfoDao().insertMobileNetworkInfo(mobileNetworkInfoEntity);
+    }
+
+    /**
+     * Query available subscription infos from the SubscriptionInfoEntity table.
+     */
+    public LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos() {
+        return mSubscriptionInfoDao().queryAvailableSubInfos();
+    }
+
+    /**
+     * Query the subscription info by the subscription ID from the SubscriptionInfoEntity
+     * table.
+     */
+    public LiveData<SubscriptionInfoEntity> querySubInfoById(String id) {
+        return mSubscriptionInfoDao().querySubInfoById(id);
+    }
+
+    /**
+     * Query all mobileNetwork infos from the MobileNetworkInfoEntity
+     * table.
+     */
+    public LiveData<List<MobileNetworkInfoEntity>> queryAllMobileNetworkInfo() {
+        return mMobileNetworkInfoDao().queryAllMobileNetworkInfos();
+    }
+
+    /**
+     * Query the mobileNetwork info by the subscription ID from the MobileNetworkInfoEntity
+     * table.
+     */
+    public LiveData<MobileNetworkInfoEntity> queryMobileNetworkInfoById(String id) {
+        return mMobileNetworkInfoDao().queryMobileNetworkInfoBySubId(id);
+    }
+
+    /**
+     * Query all UICC infos from the UiccInfoEntity table.
+     */
+    public LiveData<List<UiccInfoEntity>> queryAllUiccInfo() {
+        return mUiccInfoDao().queryAllUiccInfos();
+    }
+
+    /**
+     * Query the UICC info by the subscription ID from the UiccInfoEntity table.
+     */
+    public LiveData<UiccInfoEntity> queryUiccInfoById(String id) {
+        return mUiccInfoDao().queryUiccInfoById(id);
+    }
+
+    /**
+     * Delete the subscriptionInfo info by the subscription ID from the SubscriptionInfoEntity
+     * table.
+     */
+    public void deleteSubInfoBySubId(String id) {
+        mSubscriptionInfoDao().deleteBySubId(id);
+    }
+
+    /**
+     * Delete the mobileNetwork info by the subscription ID from the MobileNetworkInfoEntity
+     * table.
+     */
+    public void deleteMobileNetworkInfoBySubId(String id) {
+        mMobileNetworkInfoDao().deleteBySubId(id);
+    }
+
+    /**
+     * Delete the UICC info by the subscription ID from the UiccInfoEntity table.
+     */
+    public void deleteUiccInfoBySubId(String id) {
+        mUiccInfoDao().deleteBySubId(id);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java
new file mode 100644
index 0000000..299a445
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.dataservice;
+
+import java.util.List;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+@Dao
+public interface MobileNetworkInfoDao {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertMobileNetworkInfo(MobileNetworkInfoEntity... mobileNetworkInfo);
+
+    @Query("SELECT * FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " ORDER BY "
+            + DataServiceUtils.MobileNetworkInfoData.COLUMN_ID)
+    LiveData<List<MobileNetworkInfoEntity>> queryAllMobileNetworkInfos();
+
+    @Query("SELECT * FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.MobileNetworkInfoData.COLUMN_ID + " = :subId")
+    LiveData<MobileNetworkInfoEntity> queryMobileNetworkInfoBySubId(String subId);
+
+    @Query("SELECT * FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED
+            + " = :isMobileDataEnabled")
+    LiveData<List<MobileNetworkInfoEntity>> queryMobileNetworkInfosByMobileDataStatus(
+            boolean isMobileDataEnabled);
+
+    @Query("SELECT COUNT(*) FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME)
+    int count();
+
+    @Query("DELETE FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.MobileNetworkInfoData.COLUMN_ID + " = :subId")
+    void deleteBySubId(String subId);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
new file mode 100644
index 0000000..a12e0c8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.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.settingslib.mobile.dataservice;
+
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity(tableName = DataServiceUtils.MobileNetworkInfoData.TABLE_NAME)
+public class MobileNetworkInfoEntity {
+
+    public MobileNetworkInfoEntity(@NonNull String subId, boolean isContactDiscoveryEnabled,
+            boolean isContactDiscoveryVisible, boolean isMobileDataEnabled, boolean isCdmaOptions,
+            boolean isGsmOptions, boolean isWorldMode, boolean shouldDisplayNetworkSelectOptions,
+            boolean isTdscdmaSupported, boolean activeNetworkIsCellular,
+            boolean showToggleForPhysicalSim) {
+        this.subId = subId;
+        this.isContactDiscoveryEnabled = isContactDiscoveryEnabled;
+        this.isContactDiscoveryVisible = isContactDiscoveryVisible;
+        this.isMobileDataEnabled = isMobileDataEnabled;
+        this.isCdmaOptions = isCdmaOptions;
+        this.isGsmOptions = isGsmOptions;
+        this.isWorldMode = isWorldMode;
+        this.shouldDisplayNetworkSelectOptions = shouldDisplayNetworkSelectOptions;
+        this.isTdscdmaSupported = isTdscdmaSupported;
+        this.activeNetworkIsCellular = activeNetworkIsCellular;
+        this.showToggleForPhysicalSim = showToggleForPhysicalSim;
+    }
+
+    @PrimaryKey
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_ID, index = true)
+    @NonNull
+    public String subId;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_ENABLED)
+    public boolean isContactDiscoveryEnabled;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_VISIBLE)
+    public boolean isContactDiscoveryVisible;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED)
+    public boolean isMobileDataEnabled;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CDMA_OPTIONS)
+    public boolean isCdmaOptions;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_GSM_OPTIONS)
+    public boolean isGsmOptions;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_WORLD_MODE)
+    public boolean isWorldMode;
+
+    @ColumnInfo(name =
+            DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS)
+    public boolean shouldDisplayNetworkSelectOptions;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_TDSCDMA_SUPPORTED)
+    public boolean isTdscdmaSupported;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_ACTIVE_NETWORK_IS_CELLULAR)
+    public boolean activeNetworkIsCellular;
+
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM)
+    public boolean showToggleForPhysicalSim;
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(" {MobileNetworkInfoEntity(subId = ")
+                .append(subId)
+                .append(", isContactDiscoveryEnabled = ")
+                .append(isContactDiscoveryEnabled)
+                .append(", isContactDiscoveryVisible = ")
+                .append(isContactDiscoveryVisible)
+                .append(", isMobileDataEnabled = ")
+                .append(isMobileDataEnabled)
+                .append(", isCdmaOptions = ")
+                .append(isCdmaOptions)
+                .append(", isGsmOptions = ")
+                .append(isGsmOptions)
+                .append(", isWorldMode = ")
+                .append(isWorldMode)
+                .append(", shouldDisplayNetworkSelectOptions = ")
+                .append(shouldDisplayNetworkSelectOptions)
+                .append(", isTdscdmaSupported = ")
+                .append(isTdscdmaSupported)
+                .append(", activeNetworkIsCellular = ")
+                .append(activeNetworkIsCellular)
+                .append(", showToggleForPhysicalSim = ")
+                .append(showToggleForPhysicalSim)
+                .append(")}");
+        return builder.toString();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
new file mode 100644
index 0000000..4596637
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.dataservice;
+
+import java.util.List;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+@Dao
+public interface SubscriptionInfoDao {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo);
+
+    @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " ORDER BY "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_ID)
+    LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos();
+
+    @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_ID + " = :subId")
+    LiveData<SubscriptionInfoEntity> querySubInfoById(String subId);
+
+    @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID
+            + " = :isActiveSubscription" + " AND "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE
+            + " = :isSubscriptionVisible")
+    LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos(
+            boolean isActiveSubscription, boolean isSubscriptionVisible);
+
+    @Query("SELECT COUNT(*) FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME)
+    int count();
+
+    @Query("DELETE FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.SubscriptionInfoData.COLUMN_ID + " = :id")
+    void deleteBySubId(String id);
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
new file mode 100644
index 0000000..329bd9b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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;
+
+@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,
+            boolean isFirstRemovableSubscription, String defaultSimConfig,
+            boolean isDefaultSubscriptionSelection, boolean isValidSubscription,
+            boolean isUsableSubscription, boolean isActiveSubscriptionId,
+            boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
+            boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
+            boolean isDefaultSubscription) {
+        this.subId = subId;
+        this.simSlotIndex = simSlotIndex;
+        this.carrierId = carrierId;
+        this.displayName = displayName;
+        this.carrierName = carrierName;
+        this.dataRoaming = dataRoaming;
+        this.mcc = mcc;
+        this.mnc = mnc;
+        this.countryIso = countryIso;
+        this.isEmbedded = isEmbedded;
+        this.cardId = cardId;
+        this.portIndex = portIndex;
+        this.isOpportunistic = isOpportunistic;
+        this.groupUUID = groupUUID;
+        this.subscriptionType = subscriptionType;
+        this.uniqueName = uniqueName;
+        this.isSubscriptionVisible = isSubscriptionVisible;
+        this.formattedPhoneNumber = formattedPhoneNumber;
+        this.isFirstRemovableSubscription = isFirstRemovableSubscription;
+        this.defaultSimConfig = defaultSimConfig;
+        this.isDefaultSubscriptionSelection = isDefaultSubscriptionSelection;
+        this.isValidSubscription = isValidSubscription;
+        this.isUsableSubscription = isUsableSubscription;
+        this.isActiveSubscriptionId = isActiveSubscriptionId;
+        this.isAvailableSubscription = isAvailableSubscription;
+        this.isDefaultVoiceSubscription = isDefaultVoiceSubscription;
+        this.isDefaultSmsSubscription = isDefaultSmsSubscription;
+        this.isDefaultDataSubscription = isDefaultDataSubscription;
+        this.isDefaultSubscription = isDefaultSubscription;
+    }
+
+    @PrimaryKey
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_ID, index = true)
+    @NonNull
+    public String subId;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
+    public int simSlotIndex;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARRIER_ID)
+    public int carrierId;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DISPLAY_NAME)
+    public String displayName;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARRIER_NAME)
+    public String carrierName;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DATA_ROAMING)
+    public int dataRoaming;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_MCC)
+    public String mcc;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_MNC)
+    public String mnc;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_COUNTRY_ISO)
+    public String countryIso;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_EMBEDDED)
+    public boolean isEmbedded;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_CARD_ID)
+    public int cardId;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_PORT_INDEX)
+    public int portIndex;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_OPPORTUNISTIC)
+    public boolean isOpportunistic;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_GROUP_UUID)
+    @Nullable
+    public String groupUUID;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_SUBSCRIPTION_TYPE)
+    public int subscriptionType;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_UNIQUE_NAME)
+    public String uniqueName;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE)
+    public boolean isSubscriptionVisible;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_FORMATTED_PHONE_NUMBER)
+    public String formattedPhoneNumber;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION)
+    public boolean isFirstRemovableSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DEFAULT_SIM_CONFIG)
+    public String defaultSimConfig;
+
+    @ColumnInfo(name =
+            DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION)
+    public boolean isDefaultSubscriptionSelection;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_VALID_SUBSCRIPTION)
+    public boolean isValidSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_USABLE_SUBSCRIPTION)
+    public boolean isUsableSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID)
+    public boolean isActiveSubscriptionId;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_AVAILABLE_SUBSCRIPTION)
+    public boolean isAvailableSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION)
+    public boolean isDefaultVoiceSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION)
+    public boolean isDefaultSmsSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION)
+    public boolean isDefaultDataSubscription;
+
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
+    public boolean isDefaultSubscription;
+
+    public int getSubId() {
+        return Integer.valueOf(subId);
+    }
+
+    public CharSequence getUniqueDisplayName() {
+        return uniqueName;
+    }
+
+    public boolean isActiveSubscription() {
+        return isActiveSubscriptionId;
+    }
+
+    public boolean isSubscriptionVisible() {
+        return isSubscriptionVisible;
+    }
+
+    @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 + defaultSimConfig.hashCode();
+        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(isDefaultVoiceSubscription);
+        result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
+        result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
+        result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SubscriptionInfoEntity)) {
+            return false;
+        }
+
+        SubscriptionInfoEntity info = (SubscriptionInfoEntity) obj;
+        return  TextUtils.equals(subId, info.subId)
+                && simSlotIndex == info.simSlotIndex
+                && carrierId == info.carrierId
+                && TextUtils.equals(displayName, info.displayName)
+                && TextUtils.equals(carrierName, info.carrierName)
+                && dataRoaming == info.dataRoaming
+                && TextUtils.equals(mcc, info.mcc)
+                && TextUtils.equals(mnc, info.mnc)
+                && TextUtils.equals(countryIso, info.countryIso)
+                && isEmbedded == info.isEmbedded
+                && cardId == info.cardId
+                && portIndex == info.portIndex
+                && isOpportunistic == info.isOpportunistic
+                && TextUtils.equals(groupUUID, info.groupUUID)
+                && subscriptionType == info.subscriptionType
+                && TextUtils.equals(uniqueName, info.uniqueName)
+                && isSubscriptionVisible == info.isSubscriptionVisible
+                && TextUtils.equals(formattedPhoneNumber, info.formattedPhoneNumber)
+                && isFirstRemovableSubscription == info.isFirstRemovableSubscription
+                && TextUtils.equals(defaultSimConfig, info.defaultSimConfig)
+                && isDefaultSubscriptionSelection == info.isDefaultSubscriptionSelection
+                && isValidSubscription == info.isValidSubscription
+                && isUsableSubscription == info.isUsableSubscription
+                && isActiveSubscriptionId == info.isActiveSubscriptionId
+                && isAvailableSubscription == info.isAvailableSubscription
+                && isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
+                && isDefaultSmsSubscription == info.isDefaultSmsSubscription
+                && isDefaultDataSubscription == info.isDefaultDataSubscription
+                && isDefaultSubscription == info.isDefaultSubscription;
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(" {SubscriptionInfoEntity(subId = ")
+                .append(subId)
+                .append(", simSlotIndex = ")
+                .append(simSlotIndex)
+                .append(", carrierId = ")
+                .append(carrierId)
+                .append(", displayName = ")
+                .append(displayName)
+                .append(", carrierName = ")
+                .append(carrierName)
+                .append(", dataRoaming = ")
+                .append(dataRoaming)
+                .append(", mcc = ")
+                .append(mcc)
+                .append(", mnc = ")
+                .append(mnc)
+                .append(", countryIso = ")
+                .append(countryIso)
+                .append(", isEmbedded = ")
+                .append(isEmbedded)
+                .append(", cardId = ")
+                .append(cardId)
+                .append(", portIndex = ")
+                .append(portIndex)
+                .append(", isOpportunistic = ")
+                .append(isOpportunistic)
+                .append(", groupUUID = ")
+                .append(groupUUID)
+                .append(", subscriptionType = ")
+                .append(subscriptionType)
+                .append(", uniqueName = ")
+                .append(uniqueName)
+                .append(", isSubscriptionVisible = ")
+                .append(isSubscriptionVisible)
+                .append(", formattedPhoneNumber = ")
+                .append(formattedPhoneNumber)
+                .append(", isFirstRemovableSubscription = ")
+                .append(isFirstRemovableSubscription)
+                .append(", defaultSimConfig = ")
+                .append(defaultSimConfig)
+                .append(", isDefaultSubscriptionSelection = ")
+                .append(isDefaultSubscriptionSelection)
+                .append(", isValidSubscription = ")
+                .append(isValidSubscription)
+                .append(", isUsableSubscription = ")
+                .append(isUsableSubscription)
+                .append(", isActiveSubscriptionId = ")
+                .append(isActiveSubscriptionId)
+                .append(", isAvailableSubscription = ")
+                .append(isAvailableSubscription)
+                .append(", isDefaultVoiceSubscription = ")
+                .append(isDefaultVoiceSubscription)
+                .append(", isDefaultSmsSubscription = ")
+                .append(isDefaultSmsSubscription)
+                .append(", isDefaultDataSubscription = ")
+                .append(isDefaultDataSubscription)
+                .append(", isDefaultSubscription = ")
+                .append(isDefaultSubscription)
+                .append(")}");
+        return builder.toString();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
new file mode 100644
index 0000000..7e60421
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
@@ -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 com.android.settingslib.mobile.dataservice;
+
+import java.util.List;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+@Dao
+public interface UiccInfoDao {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertUiccInfo(UiccInfoEntity... uiccInfo);
+
+    @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " ORDER BY "
+            + DataServiceUtils.UiccInfoData.COLUMN_ID)
+    LiveData<List<UiccInfoEntity>> queryAllUiccInfos();
+
+    @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.UiccInfoData.COLUMN_ID + " = :subId")
+    LiveData<UiccInfoEntity> queryUiccInfoById(String subId);
+
+    @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC + " = :isEuicc")
+    LiveData<List<UiccInfoEntity>> queryUiccInfosByEuicc(boolean isEuicc);
+
+    @Query("SELECT COUNT(*) FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME)
+    int count();
+
+    @Query("DELETE FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
+            + DataServiceUtils.UiccInfoData.COLUMN_ID + " = :id")
+    void deleteBySubId(String id);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
new file mode 100644
index 0000000..532462b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.dataservice;
+
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity(tableName = DataServiceUtils.UiccInfoData.TABLE_NAME)
+public class UiccInfoEntity {
+
+    public UiccInfoEntity(@NonNull String subId, @NonNull String physicalSlotIndex,
+            int logicalSlotIndex, int cardId, boolean isEuicc,
+            boolean isMultipleEnabledProfilesSupported, int cardState, boolean isRemovable,
+            boolean isActive, int portIndex) {
+        this.subId = subId;
+        this.physicalSlotIndex = physicalSlotIndex;
+        this.logicalSlotIndex = logicalSlotIndex;
+        this.cardId = cardId;
+        this.isEuicc = isEuicc;
+        this.isMultipleEnabledProfilesSupported = isMultipleEnabledProfilesSupported;
+        this.cardState = cardState;
+        this.isRemovable = isRemovable;
+        this.isActive = isActive;
+        this.portIndex = portIndex;
+    }
+
+    @PrimaryKey
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_ID, index = true)
+    @NonNull
+    public String subId;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PHYSICAL_SLOT_INDEX)
+    @NonNull
+    public String physicalSlotIndex;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_LOGICAL_SLOT_INDEX)
+    public int logicalSlotIndex;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_ID)
+    public int cardId;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_EUICC)
+    public boolean isEuicc;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_MULTIPLE_ENABLED_PROFILES_SUPPORTED)
+    public boolean isMultipleEnabledProfilesSupported;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_CARD_STATE)
+    public int cardState;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_REMOVABLE)
+    public boolean isRemovable;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_ACTIVE)
+    public boolean isActive;
+
+    @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PORT_INDEX)
+    public int portIndex;
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(" {UiccInfoEntity(subId = ")
+                .append(subId)
+                .append(", logicalSlotIndex = ")
+                .append(physicalSlotIndex)
+                .append(", logicalSlotIndex = ")
+                .append(logicalSlotIndex)
+                .append(", cardId = ")
+                .append(cardId)
+                .append(", isEuicc = ")
+                .append(isEuicc)
+                .append(", isMultipleEnabledProfilesSupported = ")
+                .append(isMultipleEnabledProfilesSupported)
+                .append(", cardState = ")
+                .append(cardState)
+                .append(", isRemovable = ")
+                .append(isRemovable)
+                .append(", isActive = ")
+                .append(isActive)
+                .append(", portIndex = ")
+                .append(portIndex)
+                .append(")}");
+        return builder.toString();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1c0ea1a..ca14573 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -205,4 +205,45 @@
     public void isAdvancedDetailsHeader_noMetadata_returnFalse() {
         assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false);
     }
+
+    @Test
+    public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
+        when(mBluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+                BOOL_METADATA.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+    }
+
+    @Test
+    public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() {
+        when(mBluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+                BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+    }
+
+    @Test
+    public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() {
+        when(mBluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+                BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() {
+        when(mBluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+                BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+    }
 }
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 79e9938..315ab0a 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
@@ -1069,4 +1069,80 @@
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
     }
+
+    @Test
+    public void isBusy_mainDeviceIsConnecting_returnsBusy() {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+        assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+        assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mCachedDevice.isBusy()).isTrue();
+    }
+
+    @Test
+    public void isBusy_mainDeviceIsBonding_returnsBusy() {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+        assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+        assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mCachedDevice.isBusy()).isTrue();
+    }
+
+    @Test
+    public void isBusy_memberDeviceIsConnecting_returnsBusy() {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+        assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+        assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mCachedDevice.isBusy()).isTrue();
+    }
+
+    @Test
+    public void isBusy_memberDeviceIsBonding_returnsBusy() {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+        assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+        assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mCachedDevice.isBusy()).isTrue();
+    }
+
+    @Test
+    public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+        assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+        assertThat(mCachedDevice.isBusy()).isFalse();
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index f501682..1a76943 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -44,8 +44,6 @@
         Settings.System.DIM_SCREEN,
         Settings.System.SCREEN_OFF_TIMEOUT,
         Settings.System.SCREEN_BRIGHTNESS_MODE,
-        Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-        Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
         Settings.System.ADAPTIVE_SLEEP,             // moved to secure
         Settings.System.APPLY_RAMPING_RINGER,
         Settings.System.VIBRATE_INPUT_DEVICES,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b1979c9..8f6924c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -100,7 +100,9 @@
                     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.SCREEN_BRIGHTNESS_FOR_VR,
                     Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
                     );
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6c17036..ddfac36 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -684,6 +684,9 @@
     <!-- Permission required for CTS test - Notification test suite -->
     <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
 
+    <!-- Permission required for test - CellBroadcastComplianceTest -->
+    <uses-permission android:name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" />
+
     <!-- Permission required for CTS test - CaptioningManagerTest -->
     <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
 
@@ -705,6 +708,9 @@
     <!-- Permission required for CTS test - CtsKeystoreTestCases -->
     <uses-permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" />
 
+    <!-- Permission required for CTS test - CtsWindowManagerDeviceTestCases-->
+    <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/res/values-ro/strings.xml b/packages/Shell/res/values-ro/strings.xml
index 91b0b1e..56e9ee0 100644
--- a/packages/Shell/res/values-ro/strings.xml
+++ b/packages/Shell/res/values-ro/strings.xml
@@ -21,14 +21,14 @@
     <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Raportul de eroare <xliff:g id="ID">#%d</xliff:g> se generează"</string>
     <string name="bugreport_finished_title" msgid="4429132808670114081">"Raportul de eroare <xliff:g id="ID">#%d</xliff:g> a fost creat"</string>
     <string name="bugreport_updating_title" msgid="4423539949559634214">"Se adaugă detaliile la raportul de eroare"</string>
-    <string name="bugreport_updating_wait" msgid="3322151947853929470">"Așteptați…"</string>
+    <string name="bugreport_updating_wait" msgid="3322151947853929470">"Te rugăm să aștepți…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Raportul de eroare va apărea curând pe telefon"</string>
-    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selectați pentru a trimite raportul de eroare"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Atingeți pentru a trimite raportul de eroare"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Selectați pentru a trimite raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Atingeți ca să trimiteți raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Atingeți ca să trimiteți raportul de eroare fără captură de ecran sau așteptați finalizarea acesteia"</string>
-    <string name="bugreport_confirm" msgid="5917407234515812495">"Rapoartele despre erori conțin date din diferite fișiere de jurnal ale sistemului. Acestea pot include date pe care le puteți considera sensibile (cum ar fi utilizarea aplicației și date despre locație). Permiteți accesul la rapoartele despre erori numai aplicațiilor și persoanelor în care aveți încredere."</string>
+    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selectează pentru a trimite raportul de eroare"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Atinge pentru a trimite raportul de eroare"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Selectează pentru a trimite raportul fără captură de ecran sau așteaptă finalizarea acesteia"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Atinge ca să trimiți raportul de eroare fără captură de ecran sau așteaptă finalizarea acesteia"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Atinge ca să trimiți raportul de eroare fără captură de ecran sau așteaptă finalizarea acesteia"</string>
+    <string name="bugreport_confirm" msgid="5917407234515812495">"Rapoartele despre erori conțin date din diferite fișiere de jurnal ale sistemului. Acestea pot include date pe care le poți considera sensibile (cum ar fi utilizarea aplicației și date despre locație). Permite accesul la rapoartele despre erori numai aplicațiilor și persoanelor în care ai încredere."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Nu mai afișa"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Rapoarte de erori"</string>
     <string name="bugreport_unreadable_text" msgid="586517851044535486">"Fișierul cu raportul de eroare nu a putut fi citit"</string>
@@ -42,6 +42,6 @@
     <string name="bugreport_info_name" msgid="4414036021935139527">"Numele fișierului"</string>
     <string name="bugreport_info_title" msgid="2306030793918239804">"Titlul erorii"</string>
     <string name="bugreport_info_description" msgid="5072835127481627722">"Rezumat privind eroarea"</string>
-    <string name="save" msgid="4781509040564835759">"Salvați"</string>
-    <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"Trimiteți raportul de eroare"</string>
+    <string name="save" msgid="4781509040564835759">"Salvează"</string>
+    <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"Trimite raportul de eroare"</string>
 </resources>
diff --git a/packages/Shell/src/com/android/shell/Screenshooter.java b/packages/Shell/src/com/android/shell/Screenshooter.java
index d55eda0..baaddf5 100644
--- a/packages/Shell/src/com/android/shell/Screenshooter.java
+++ b/packages/Shell/src/com/android/shell/Screenshooter.java
@@ -16,11 +16,17 @@
 
 package com.android.shell;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.graphics.Bitmap;
-import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
-import android.view.SurfaceControl;
+import android.util.Pair;
+import android.view.WindowManagerGlobal;
 import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
 
 /**
  * Helper class used to take screenshots.
@@ -40,12 +46,15 @@
     static Bitmap takeScreenshot() {
         Log.d(TAG, "Taking fullscreen screenshot");
         // Take the screenshot
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        final ScreenCapture.DisplayCaptureArgs captureArgs =
-                new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
-                        .build();
-        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                ScreenCapture.captureDisplay(captureArgs);
+        final Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
+        try {
+            WindowManagerGlobal.getWindowManagerService().captureDisplay(DEFAULT_DISPLAY, null,
+                    syncScreenCapture.first);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        final ScreenshotHardwareBuffer screenshotBuffer = syncScreenCapture.second.get();
         final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (screenShot == null) {
             Log.e(TAG, "Failed to take fullscreen screenshot");
diff --git a/packages/SimAppDialog/res/values-ro/strings.xml b/packages/SimAppDialog/res/values-ro/strings.xml
index 21663d1..2117191 100644
--- a/packages/SimAppDialog/res/values-ro/strings.xml
+++ b/packages/SimAppDialog/res/values-ro/strings.xml
@@ -18,9 +18,9 @@
 <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="8898068901680117589">"Sim App Dialog"</string>
-    <string name="install_carrier_app_title" msgid="334729104862562585">"Activați serviciul mobil"</string>
-    <string name="install_carrier_app_description" msgid="4014303558674923797">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalați aplicația <xliff:g id="ID_1">%1$s</xliff:g>"</string>
-    <string name="install_carrier_app_description_default" msgid="7356830245205847840">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalați aplicația operatorului"</string>
+    <string name="install_carrier_app_title" msgid="334729104862562585">"Activează serviciul mobil"</string>
+    <string name="install_carrier_app_description" msgid="4014303558674923797">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalezi aplicația <xliff:g id="ID_1">%1$s</xliff:g>"</string>
+    <string name="install_carrier_app_description_default" msgid="7356830245205847840">"Pentru ca noul card SIM să funcționeze corect, va trebui să instalezi aplicația operatorului"</string>
     <string name="install_carrier_app_defer_action" msgid="2558576736886876209">"Nu acum"</string>
-    <string name="install_carrier_app_download_action" msgid="7859229305958538064">"Descărcați aplicația"</string>
+    <string name="install_carrier_app_download_action" msgid="7859229305958538064">"Descarcă aplicația"</string>
 </resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b7fb472..5e0d935 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -124,6 +124,7 @@
         "dagger2",
         "jsr330",
         "lottie",
+        "LowLightDreamLib",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -228,6 +229,7 @@
         "dagger2",
         "jsr330",
         "WindowManager-Shell",
+        "LowLightDreamLib",
     ],
     libs: [
         "android.test.runner",
@@ -298,5 +300,6 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
+        "wmshell.protolog.json.gz",
     ],
 }
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 234ef24..aaee42f 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,25 +1,6 @@
 {
   // Looking for unit test presubmit configuration?
   // This currently lives in ATP config apct/system_ui/unit_test
-  "presubmit-large": [
-    {
-      "name": "PlatformScenarioTests",
-      "options": [
-        {
-            "include-filter": "android.platform.test.scenario.sysui"
-        },
-        {
-            "include-annotation": "android.platform.test.scenario.annotation.Scenario"
-        },
-        {
-            "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-            "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ]
-    }
-  ],
   "presubmit-sysui": [
     {
       "name": "PlatformScenarioTests",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
new file mode 100644
index 0000000..4eb7c7d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/** Detects usage of Context.getSystemService() and suggests to use an injected instance instead. */
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("getSystemService")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        val evaluator = context.evaluator
+        if (
+            !evaluator.isStatic(method) &&
+                method.name == "getSystemService" &&
+                method.containingClass?.qualifiedName == "android.content.Context"
+        ) {
+            context.report(
+                ISSUE,
+                method,
+                context.getNameLocation(node),
+                "Use @Inject to get the handle to a system-level services instead of using " +
+                    "Context.getSystemService()"
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "NonInjectedService",
+                briefDescription =
+                    "System-level services should be retrieved using " +
+                        "@Inject instead of Context.getSystemService().",
+                explanation =
+                    "Context.getSystemService() should be avoided because it makes testing " +
+                        "difficult. Instead, use an injected service. For example, " +
+                        "instead of calling Context.getSystemService(UserManager.class), " +
+                        "use @Inject and add UserManager to the constructor",
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(NonInjectedServiceDetector::class.java, Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index b72d03d..eb71d32 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -27,6 +27,7 @@
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UCallExpression
 
+@Suppress("UnstableApiUsage")
 class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
 
     override fun getApplicableMethodNames(): List<String> {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 4879883..312810b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -35,6 +35,7 @@
                 GetMainLooperViaContextDetector.ISSUE,
                 RegisterReceiverViaContextDetector.ISSUE,
                 SoftwareBitmapDetector.ISSUE,
+                NonInjectedServiceDetector.ISSUE,
         )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
new file mode 100644
index 0000000..26bd8d0
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+
+/*
+ * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
+ * stubs are not used in the lint detectors themselves.
+ */
+@Suppress("UnstableApiUsage")
+internal val androidStubs =
+    arrayOf(
+        java(
+            """
+package android.app;
+
+public class ActivityManager {
+    public static int getCurrentUser() {}
+}
+"""
+        ),
+        java(
+            """
+package android.os;
+import android.content.pm.UserInfo;
+import android.annotation.UserIdInt;
+
+public class UserManager {
+    public UserInfo getUserInfo(@UserIdInt int userId) {}
+}
+"""
+        ),
+        java("""
+package android.annotation;
+
+public @interface UserIdInt {}
+"""),
+        java("""
+package android.content.pm;
+
+public class UserInfo {}
+"""),
+        java("""
+package android.os;
+
+public class Looper {}
+"""),
+        java("""
+package android.os;
+
+public class Handler {}
+"""),
+        java("""
+package android.content;
+
+public class ServiceConnection {}
+"""),
+        java("""
+package android.os;
+
+public enum UserHandle {
+    ALL
+}
+"""),
+        java(
+            """
+package android.content;
+import android.os.UserHandle;
+import android.os.Handler;
+import android.os.Looper;
+import java.util.concurrent.Executor;
+
+public class Context {
+    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
+    public void registerReceiverAsUser(
+            BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {}
+    public void registerReceiverForAllUsers(
+            BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
+            Handler scheduler) {}
+    public void sendBroadcast(Intent intent) {}
+    public void sendBroadcast(Intent intent, String receiverPermission) {}
+    public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
+    public void bindService(Intent intent) {}
+    public void bindServiceAsUser(
+            Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
+    public void unbindService(ServiceConnection connection) {}
+    public Looper getMainLooper() { return null; }
+    public Executor getMainExecutor() { return null; }
+    public Handler getMainThreadHandler() { return null; }
+    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
+    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+}
+"""
+        ),
+        java(
+            """
+package android.app;
+import android.content.Context;
+
+public class Activity extends Context {}
+"""
+        ),
+        java(
+            """
+package android.graphics;
+
+public class Bitmap {
+    public enum Config {
+        ARGB_8888,
+        RGB_565,
+        HARDWARE
+    }
+    public static Bitmap createBitmap(int width, int height, Config config) {
+        return null;
+    }
+}
+"""
+        ),
+        java("""
+package android.content;
+
+public class BroadcastReceiver {}
+"""),
+        java("""
+package android.content;
+
+public class IntentFilter {}
+"""),
+        java(
+            """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+
+public interface UserTracker {
+    int getUserId();
+    UserInfo getUserInfo();
+}
+"""
+        ),
+    )
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
similarity index 61%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
index bf685f7..564afcb 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class BindServiceViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = BindServiceViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-            BindServiceViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE)
 
     private val explanation = "Binding or unbinding services are synchronous calls"
 
     @Test
     fun testBindService() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -49,17 +49,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testBindServiceAsUser() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -73,17 +76,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testUnbindService() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -96,45 +102,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void bindService(Intent intent) {};
-            public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
-                                          UserHandle userHandle) {};
-            public void unbindService(ServiceConnection connection) {};
-        }
-        """
-    )
-
-    private val serviceConnectionStub: TestFile = java(
-            """
-        package android.content;
-
-        public class ServiceConnection {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-            """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
similarity index 62%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index da010212f2..06aee8e 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -1,26 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = BroadcastSentViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-        BroadcastSentViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
 
     @Test
     fun testSendBroadcast() {
-        lint().files(
-            TestFiles.java(
-                """
+        println(stubs.size)
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
 
@@ -31,21 +48,25 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testSendBroadcastAsUser() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
                     import android.os.UserHandle;
@@ -56,21 +77,26 @@
                           context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
                         }
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testSendBroadcastInActivity() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.app.Activity;
                     import android.os.UserHandle;
@@ -82,21 +108,26 @@
                         }
 
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testNoopIfNoCall() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
 
@@ -106,45 +137,15 @@
                           context.startActivity(intent);
                         }
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectClean()
     }
 
-    private val contextStub: TestFile = java(
-        """
-        package android.content;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void sendBroadcast(Intent intent) {};
-            public void sendBroadcast(Intent intent, String receiverPermission) {};
-            public void sendBroadcastAsUser(Intent intent, UserHandle userHandle,
-                                                String permission) {};
-        }
-        """
-    )
-
-    private val activityStub: TestFile = java(
-        """
-        package android.app;
-        import android.content.Context;
-
-        public class Activity extends Context {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-        """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, activityStub, userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
similarity index 64%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
index ec761cd..c55f399 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -17,13 +17,13 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = GetMainLooperViaContextDetector()
@@ -35,7 +35,8 @@
 
     @Test
     fun testGetMainThreadHandler() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -48,17 +49,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testGetMainLooper() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -71,17 +75,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testGetMainExecutor() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -94,42 +101,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
-
-        public class Context {
-            public Looper getMainLooper() { return null; };
-            public Executor getMainExecutor() { return null; };
-            public Handler getMainThreadHandler() { return null; };
-        }
-        """
-    )
-
-    private val looperStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Looper {}
-        """
-    )
-
-    private val handlerStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Handler {}
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
new file mode 100644
index 0000000..6b9f88f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -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.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = NonInjectedServiceDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+    override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
+
+    @Test
+    fun testGetServiceWithString() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.content.Context;
+
+                        public class TestClass1 {
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                context.getSystemService("user");
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedServiceDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains("Use @Inject to get the handle")
+    }
+
+    @Test
+    fun testGetServiceWithClass() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.content.Context;
+                        import android.os.UserManager;
+
+                        public class TestClass2 {
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                context.getSystemService(UserManager.class);
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedServiceDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains("Use @Inject to get the handle")
+    }
+
+    private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
similarity index 60%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 76c0519..802ceba 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-            RegisterReceiverViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
 
     private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
 
     @Test
     fun testRegisterReceiver() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -51,17 +51,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testRegisterReceiverAsUser() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -79,17 +82,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testRegisterReceiverForAllUsers() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -107,65 +113,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.Handler;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
-                int flags) {};
-            public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
-                IntentFilter filter, String broadcastPermission, Handler scheduler) {};
-            public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
-                String broadcastPermission, Handler scheduler) {};
-        }
-        """
-    )
-
-    private val broadcastReceiverStub: TestFile = java(
-            """
-        package android.content;
-
-        public class BroadcastReceiver {}
-        """
-    )
-
-    private val intentFilterStub: TestFile = java(
-            """
-        package android.content;
-
-        public class IntentFilter {}
-        """
-    )
-
-    private val handlerStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Handler {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-            """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
-            userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
similarity index 74%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 2738f04..e265837 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -1,13 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class SlowUserQueryDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -134,61 +150,5 @@
             .expectClean()
     }
 
-    private val activityManagerStub: TestFile =
-        java(
-            """
-            package android.app;
-
-            public class ActivityManager {
-                public static int getCurrentUser() {};
-            }
-            """
-        )
-
-    private val userManagerStub: TestFile =
-        java(
-            """
-            package android.os;
-            import android.content.pm.UserInfo;
-            import android.annotation.UserIdInt;
-
-            public class UserManager {
-                public UserInfo getUserInfo(@UserIdInt int userId) {};
-            }
-            """
-        )
-
-    private val userIdIntStub: TestFile =
-        java(
-            """
-            package android.annotation;
-
-            public @interface UserIdInt {}
-            """
-        )
-
-    private val userInfoStub: TestFile =
-        java(
-            """
-            package android.content.pm;
-
-            public class UserInfo {}
-            """
-        )
-
-    private val userTrackerStub: TestFile =
-        java(
-            """
-            package com.android.systemui.settings;
-            import android.content.pm.UserInfo;
-
-            public interface UserTracker {
-                public int getUserId();
-                public UserInfo getUserInfo();
-            }
-            """
-        )
-
-    private val stubs =
-        arrayOf(activityManagerStub, userManagerStub, userIdIntStub, userInfoStub, userTrackerStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
similarity index 70%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 890f2b8..fd6ab09 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -17,7 +17,6 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
@@ -36,7 +35,8 @@
 
     @Test
     fun testSoftwareBitmap() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     import android.graphics.Bitmap;
@@ -48,17 +48,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(SoftwareBitmapDetector.ISSUE)
-                .run()
-                .expectWarningCount(2)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(SoftwareBitmapDetector.ISSUE)
+            .run()
+            .expectWarningCount(2)
+            .expectContains(explanation)
     }
 
     @Test
     fun testHardwareBitmap() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     import android.graphics.Bitmap;
@@ -69,29 +72,14 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(SoftwareBitmapDetector.ISSUE)
-                .run()
-                .expectWarningCount(0)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(SoftwareBitmapDetector.ISSUE)
+            .run()
+            .expectWarningCount(0)
     }
 
-    private val bitmapStub: TestFile = java(
-            """
-        package android.graphics;
-
-        public class Bitmap {
-            public enum Config {
-                ARGB_8888,
-                RGB_565,
-                HARDWARE
-            }
-            public static Bitmap createBitmap(int width, int height, Config config) {
-                return null;
-            }
-        }
-        """
-    )
-
-    private val stubs = arrayOf(bitmapStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt
new file mode 100644
index 0000000..19624e6
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/Pager.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.layout.pager
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.rememberSplineBasedDecay
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+
+/** Library-wide switch to turn on debug logging. */
+internal const val DebugLog = false
+
+@RequiresOptIn(message = "Accompanist Pager is experimental. The API may be changed in the future.")
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalPagerApi
+
+/** Contains the default values used by [HorizontalPager] and [VerticalPager]. */
+@ExperimentalPagerApi
+object PagerDefaults {
+    /**
+     * Remember the default [FlingBehavior] that represents the scroll curve.
+     *
+     * @param state The [PagerState] to update.
+     * @param decayAnimationSpec The decay animation spec to use for decayed flings.
+     * @param snapAnimationSpec The animation spec to use when snapping.
+     */
+    @Composable
+    fun flingBehavior(
+        state: PagerState,
+        decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
+        snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
+    ): FlingBehavior =
+        rememberSnappingFlingBehavior(
+            lazyListState = state.lazyListState,
+            decayAnimationSpec = decayAnimationSpec,
+            snapAnimationSpec = snapAnimationSpec,
+        )
+
+    @Deprecated(
+        "Replaced with PagerDefaults.flingBehavior()",
+        ReplaceWith("PagerDefaults.flingBehavior(state, decayAnimationSpec, snapAnimationSpec)")
+    )
+    @Composable
+    fun rememberPagerFlingConfig(
+        state: PagerState,
+        decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
+        snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
+    ): FlingBehavior = flingBehavior(state, decayAnimationSpec, snapAnimationSpec)
+}
+
+/**
+ * A horizontally scrolling layout that allows users to flip between items to the left and right.
+ *
+ * @sample com.google.accompanist.sample.pager.HorizontalPagerSample
+ *
+ * @param count the number of pages.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the pager's state.
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the end to the start and [PagerState.currentPage] == 0 will mean the first item is
+ * located at the end.
+ * @param itemSpacing horizontal spacing to add between items.
+ * @param flingBehavior logic describing fling behavior.
+ * @param key the scroll position will be maintained based on the key, which means if you add/remove
+ * items before the current visible item the item with the given key will be kept as the first
+ * visible one.
+ * @param content a block which describes the content. Inside this block you can reference
+ * [PagerScope.currentPage] and other properties in [PagerScope].
+ */
+@ExperimentalPagerApi
+@Composable
+fun HorizontalPager(
+    count: Int,
+    modifier: Modifier = Modifier,
+    state: PagerState = rememberPagerState(),
+    reverseLayout: Boolean = false,
+    itemSpacing: Dp = 0.dp,
+    flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state),
+    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
+    key: ((page: Int) -> Any)? = null,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    content: @Composable PagerScope.(page: Int) -> Unit,
+) {
+    Pager(
+        count = count,
+        state = state,
+        modifier = modifier,
+        isVertical = false,
+        reverseLayout = reverseLayout,
+        itemSpacing = itemSpacing,
+        verticalAlignment = verticalAlignment,
+        flingBehavior = flingBehavior,
+        key = key,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+/**
+ * A vertically scrolling layout that allows users to flip between items to the top and bottom.
+ *
+ * @sample com.google.accompanist.sample.pager.VerticalPagerSample
+ *
+ * @param count the number of pages.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the pager's state.
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top and [PagerState.currentPage] == 0 will mean the first item is
+ * located at the bottom.
+ * @param itemSpacing vertical spacing to add between items.
+ * @param flingBehavior logic describing fling behavior.
+ * @param key the scroll position will be maintained based on the key, which means if you add/remove
+ * items before the current visible item the item with the given key will be kept as the first
+ * visible one.
+ * @param content a block which describes the content. Inside this block you can reference
+ * [PagerScope.currentPage] and other properties in [PagerScope].
+ */
+@ExperimentalPagerApi
+@Composable
+fun VerticalPager(
+    count: Int,
+    modifier: Modifier = Modifier,
+    state: PagerState = rememberPagerState(),
+    reverseLayout: Boolean = false,
+    itemSpacing: Dp = 0.dp,
+    flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state),
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    key: ((page: Int) -> Any)? = null,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    content: @Composable PagerScope.(page: Int) -> Unit,
+) {
+    Pager(
+        count = count,
+        state = state,
+        modifier = modifier,
+        isVertical = true,
+        reverseLayout = reverseLayout,
+        itemSpacing = itemSpacing,
+        horizontalAlignment = horizontalAlignment,
+        flingBehavior = flingBehavior,
+        key = key,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+@ExperimentalPagerApi
+@Composable
+internal fun Pager(
+    count: Int,
+    modifier: Modifier,
+    state: PagerState,
+    reverseLayout: Boolean,
+    itemSpacing: Dp,
+    isVertical: Boolean,
+    flingBehavior: FlingBehavior,
+    key: ((page: Int) -> Any)?,
+    contentPadding: PaddingValues,
+    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    content: @Composable PagerScope.(page: Int) -> Unit,
+) {
+    require(count >= 0) { "pageCount must be >= 0" }
+
+    // Provide our PagerState with access to the SnappingFlingBehavior animation target
+    // TODO: can this be done in a better way?
+    state.flingAnimationTarget = { (flingBehavior as? SnappingFlingBehavior)?.animationTarget }
+
+    LaunchedEffect(count) {
+        state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
+    }
+
+    // Once a fling (scroll) has finished, notify the state
+    LaunchedEffect(state) {
+        // When a 'scroll' has finished, notify the state
+        snapshotFlow { state.isScrollInProgress }
+            .filter { !it }
+            .collect { state.onScrollFinished() }
+    }
+
+    val pagerScope = remember(state) { PagerScopeImpl(state) }
+
+    // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate
+    // as normal
+    val consumeFlingNestedScrollConnection =
+        ConsumeFlingNestedScrollConnection(
+            consumeHorizontal = !isVertical,
+            consumeVertical = isVertical,
+        )
+
+    if (isVertical) {
+        LazyColumn(
+            state = state.lazyListState,
+            verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment),
+            horizontalAlignment = horizontalAlignment,
+            flingBehavior = flingBehavior,
+            reverseLayout = reverseLayout,
+            contentPadding = contentPadding,
+            modifier = modifier,
+        ) {
+            items(
+                count = count,
+                key = key,
+            ) { page ->
+                Box(
+                    Modifier
+                        // We don't any nested flings to continue in the pager, so we add a
+                        // connection which consumes them.
+                        // See: https://github.com/google/accompanist/issues/347
+                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
+                        // Constraint the content to be <= than the size of the pager.
+                        .fillParentMaxHeight()
+                        .wrapContentSize()
+                ) { pagerScope.content(page) }
+            }
+        }
+    } else {
+        LazyRow(
+            state = state.lazyListState,
+            verticalAlignment = verticalAlignment,
+            horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment),
+            flingBehavior = flingBehavior,
+            reverseLayout = reverseLayout,
+            contentPadding = contentPadding,
+            modifier = modifier,
+        ) {
+            items(
+                count = count,
+                key = key,
+            ) { page ->
+                Box(
+                    Modifier
+                        // We don't any nested flings to continue in the pager, so we add a
+                        // connection which consumes them.
+                        // See: https://github.com/google/accompanist/issues/347
+                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
+                        // Constraint the content to be <= than the size of the pager.
+                        .fillParentMaxWidth()
+                        .wrapContentSize()
+                ) { pagerScope.content(page) }
+            }
+        }
+    }
+}
+
+private class ConsumeFlingNestedScrollConnection(
+    private val consumeHorizontal: Boolean,
+    private val consumeVertical: Boolean,
+) : NestedScrollConnection {
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset =
+        when (source) {
+            // We can consume all resting fling scrolls so that they don't propagate up to the
+            // Pager
+            NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical)
+            else -> Offset.Zero
+        }
+
+    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        // We can consume all post fling velocity on the main-axis
+        // so that it doesn't propagate up to the Pager
+        return available.consume(consumeHorizontal, consumeVertical)
+    }
+}
+
+private fun Offset.consume(
+    consumeHorizontal: Boolean,
+    consumeVertical: Boolean,
+): Offset =
+    Offset(
+        x = if (consumeHorizontal) this.x else 0f,
+        y = if (consumeVertical) this.y else 0f,
+    )
+
+private fun Velocity.consume(
+    consumeHorizontal: Boolean,
+    consumeVertical: Boolean,
+): Velocity =
+    Velocity(
+        x = if (consumeHorizontal) this.x else 0f,
+        y = if (consumeVertical) this.y else 0f,
+    )
+
+/** Scope for [HorizontalPager] content. */
+@ExperimentalPagerApi
+@Stable
+interface PagerScope {
+    /** Returns the current selected page */
+    val currentPage: Int
+
+    /** The current offset from the start of [currentPage], as a ratio of the page width. */
+    val currentPageOffset: Float
+}
+
+@ExperimentalPagerApi
+private class PagerScopeImpl(
+    private val state: PagerState,
+) : PagerScope {
+    override val currentPage: Int
+        get() = state.currentPage
+    override val currentPageOffset: Float
+        get() = state.currentPageOffset
+}
+
+/**
+ * Calculate the offset for the given [page] from the current scroll position. This is useful when
+ * using the scroll position to apply effects or animations to items.
+ *
+ * The returned offset can positive or negative, depending on whether which direction the [page] is
+ * compared to the current scroll position.
+ *
+ * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition
+ */
+@ExperimentalPagerApi
+fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float {
+    return (currentPage + currentPageOffset) - page
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt
new file mode 100644
index 0000000..288c26e
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/PagerState.kt
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.layout.pager
+
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.lazy.LazyListItemInfo
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
+
+@Deprecated(
+    "Replaced with rememberPagerState(initialPage) and count parameter on Pager composables",
+    ReplaceWith("rememberPagerState(initialPage)"),
+    level = DeprecationLevel.ERROR,
+)
+@Suppress("UNUSED_PARAMETER", "NOTHING_TO_INLINE")
+@ExperimentalPagerApi
+@Composable
+inline fun rememberPagerState(
+    @IntRange(from = 0) pageCount: Int,
+    @IntRange(from = 0) initialPage: Int = 0,
+    @FloatRange(from = 0.0, to = 1.0) initialPageOffset: Float = 0f,
+    @IntRange(from = 1) initialOffscreenLimit: Int = 1,
+    infiniteLoop: Boolean = false
+): PagerState {
+    return rememberPagerState(initialPage = initialPage)
+}
+
+/**
+ * Creates a [PagerState] that is remembered across compositions.
+ *
+ * Changes to the provided values for [initialPage] will **not** result in the state being recreated
+ * or changed in any way if it has already been created.
+ *
+ * @param initialPage the initial value for [PagerState.currentPage]
+ */
+@ExperimentalPagerApi
+@Composable
+fun rememberPagerState(
+    @IntRange(from = 0) initialPage: Int = 0,
+): PagerState =
+    rememberSaveable(saver = PagerState.Saver) {
+        PagerState(
+            currentPage = initialPage,
+        )
+    }
+
+/**
+ * A state object that can be hoisted to control and observe scrolling for [HorizontalPager].
+ *
+ * In most cases, this will be created via [rememberPagerState].
+ *
+ * @param currentPage the initial value for [PagerState.currentPage]
+ */
+@ExperimentalPagerApi
+@Stable
+class PagerState(
+    @IntRange(from = 0) currentPage: Int = 0,
+) : ScrollableState {
+    // Should this be public?
+    internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
+
+    private var _currentPage by mutableStateOf(currentPage)
+
+    private val currentLayoutPageInfo: LazyListItemInfo?
+        get() =
+            lazyListState.layoutInfo.visibleItemsInfo
+                .asSequence()
+                .filter { it.offset <= 0 && it.offset + it.size > 0 }
+                .lastOrNull()
+
+    private val currentLayoutPageOffset: Float
+        get() =
+            currentLayoutPageInfo?.let { current ->
+                // We coerce since itemSpacing can make the offset > 1f.
+                // We don't want to count spacing in the offset so cap it to 1f
+                (-current.offset / current.size.toFloat()).coerceIn(0f, 1f)
+            }
+                ?: 0f
+
+    /**
+     * [InteractionSource] that will be used to dispatch drag events when this list is being
+     * dragged. If you want to know whether the fling (or animated scroll) is in progress, use
+     * [isScrollInProgress].
+     */
+    val interactionSource: InteractionSource
+        get() = lazyListState.interactionSource
+
+    /** The number of pages to display. */
+    @get:IntRange(from = 0)
+    val pageCount: Int by derivedStateOf { lazyListState.layoutInfo.totalItemsCount }
+
+    /**
+     * The index of the currently selected page. This may not be the page which is currently
+     * displayed on screen.
+     *
+     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
+     */
+    @get:IntRange(from = 0)
+    var currentPage: Int
+        get() = _currentPage
+        internal set(value) {
+            if (value != _currentPage) {
+                _currentPage = value
+            }
+        }
+
+    /**
+     * The current offset from the start of [currentPage], as a ratio of the page width.
+     *
+     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
+     */
+    val currentPageOffset: Float by derivedStateOf {
+        currentLayoutPageInfo?.let {
+            // The current page offset is the current layout page delta from `currentPage`
+            // (which is only updated after a scroll/animation).
+            // We calculate this by looking at the current layout page + it's offset,
+            // then subtracting the 'current page'.
+            it.index + currentLayoutPageOffset - _currentPage
+        }
+            ?: 0f
+    }
+
+    /** The target page for any on-going animations. */
+    private var animationTargetPage: Int? by mutableStateOf(null)
+
+    internal var flingAnimationTarget: (() -> Int?)? by mutableStateOf(null)
+
+    /**
+     * The target page for any on-going animations or scrolls by the user. Returns the current page
+     * if a scroll or animation is not currently in progress.
+     */
+    val targetPage: Int
+        get() =
+            animationTargetPage
+                ?: flingAnimationTarget?.invoke()
+                    ?: when {
+                    // If a scroll isn't in progress, return the current page
+                    !isScrollInProgress -> currentPage
+                    // If the offset is 0f (or very close), return the current page
+                    currentPageOffset.absoluteValue < 0.001f -> currentPage
+                    // If we're offset towards the start, guess the previous page
+                    currentPageOffset < -0.5f -> (currentPage - 1).coerceAtLeast(0)
+                    // If we're offset towards the end, guess the next page
+                    else -> (currentPage + 1).coerceAtMost(pageCount - 1)
+                }
+
+    @Deprecated(
+        "Replaced with animateScrollToPage(page, pageOffset)",
+        ReplaceWith("animateScrollToPage(page = page, pageOffset = pageOffset)")
+    )
+    @Suppress("UNUSED_PARAMETER")
+    suspend fun animateScrollToPage(
+        @IntRange(from = 0) page: Int,
+        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
+        animationSpec: AnimationSpec<Float> = spring(),
+        initialVelocity: Float = 0f,
+        skipPages: Boolean = true,
+    ) {
+        animateScrollToPage(page = page, pageOffset = pageOffset)
+    }
+
+    /**
+     * Animate (smooth scroll) to the given page to the middle of the viewport.
+     *
+     * Cancels the currently running scroll, if any, and suspends until the cancellation is
+     * complete.
+     *
+     * @param page the page to animate to. Must be between 0 and [pageCount] (inclusive).
+     * @param pageOffset the percentage of the page width to offset, from the start of [page]. Must
+     * be in the range 0f..1f.
+     */
+    suspend fun animateScrollToPage(
+        @IntRange(from = 0) page: Int,
+        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
+    ) {
+        requireCurrentPage(page, "page")
+        requireCurrentPageOffset(pageOffset, "pageOffset")
+        try {
+            animationTargetPage = page
+
+            if (pageOffset <= 0.005f) {
+                // If the offset is (close to) zero, just call animateScrollToItem and we're done
+                lazyListState.animateScrollToItem(index = page)
+            } else {
+                // Else we need to figure out what the offset is in pixels...
+
+                var target =
+                    lazyListState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
+
+                if (target != null) {
+                    // If we have access to the target page layout, we can calculate the pixel
+                    // offset from the size
+                    lazyListState.animateScrollToItem(
+                        index = page,
+                        scrollOffset = (target.size * pageOffset).roundToInt()
+                    )
+                } else {
+                    // If we don't, we use the current page size as a guide
+                    val currentSize = currentLayoutPageInfo!!.size
+                    lazyListState.animateScrollToItem(
+                        index = page,
+                        scrollOffset = (currentSize * pageOffset).roundToInt()
+                    )
+
+                    // The target should be visible now
+                    target = lazyListState.layoutInfo.visibleItemsInfo.first { it.index == page }
+
+                    if (target.size != currentSize) {
+                        // If the size we used for calculating the offset differs from the actual
+                        // target page size, we need to scroll again. This doesn't look great,
+                        // but there's not much else we can do.
+                        lazyListState.animateScrollToItem(
+                            index = page,
+                            scrollOffset = (target.size * pageOffset).roundToInt()
+                        )
+                    }
+                }
+            }
+        } finally {
+            // We need to manually call this, as the `animateScrollToItem` call above will happen
+            // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect
+            // the change. This is especially true when running unit tests.
+            onScrollFinished()
+        }
+    }
+
+    /**
+     * Instantly brings the item at [page] to the middle of the viewport.
+     *
+     * Cancels the currently running scroll, if any, and suspends until the cancellation is
+     * complete.
+     *
+     * @param page the page to snap to. Must be between 0 and [pageCount] (inclusive).
+     */
+    suspend fun scrollToPage(
+        @IntRange(from = 0) page: Int,
+        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
+    ) {
+        requireCurrentPage(page, "page")
+        requireCurrentPageOffset(pageOffset, "pageOffset")
+        try {
+            animationTargetPage = page
+
+            // First scroll to the given page. It will now be laid out at offset 0
+            lazyListState.scrollToItem(index = page)
+
+            // If we have a start spacing, we need to offset (scroll) by that too
+            if (pageOffset > 0.0001f) {
+                scroll { currentLayoutPageInfo?.let { scrollBy(it.size * pageOffset) } }
+            }
+        } finally {
+            // We need to manually call this, as the `scroll` call above will happen in 1 frame,
+            // which is usually too fast for the LaunchedEffect in Pager to detect the change.
+            // This is especially true when running unit tests.
+            onScrollFinished()
+        }
+    }
+
+    internal fun onScrollFinished() {
+        // Then update the current page to our layout page
+        currentPage = currentLayoutPageInfo?.index ?: 0
+        // Clear the animation target page
+        animationTargetPage = null
+    }
+
+    override suspend fun scroll(
+        scrollPriority: MutatePriority,
+        block: suspend ScrollScope.() -> Unit
+    ) = lazyListState.scroll(scrollPriority, block)
+
+    override fun dispatchRawDelta(delta: Float): Float {
+        return lazyListState.dispatchRawDelta(delta)
+    }
+
+    override val isScrollInProgress: Boolean
+        get() = lazyListState.isScrollInProgress
+
+    override fun toString(): String =
+        "PagerState(" +
+            "pageCount=$pageCount, " +
+            "currentPage=$currentPage, " +
+            "currentPageOffset=$currentPageOffset" +
+            ")"
+
+    private fun requireCurrentPage(value: Int, name: String) {
+        if (pageCount == 0) {
+            require(value == 0) { "$name must be 0 when pageCount is 0" }
+        } else {
+            require(value in 0 until pageCount) { "$name[$value] must be >= 0 and < pageCount" }
+        }
+    }
+
+    private fun requireCurrentPageOffset(value: Float, name: String) {
+        if (pageCount == 0) {
+            require(value == 0f) { "$name must be 0f when pageCount is 0" }
+        } else {
+            require(value in 0f..1f) { "$name must be >= 0 and <= 1" }
+        }
+    }
+
+    companion object {
+        /** The default [Saver] implementation for [PagerState]. */
+        val Saver: Saver<PagerState, *> =
+            listSaver(
+                save = {
+                    listOf<Any>(
+                        it.currentPage,
+                    )
+                },
+                restore = {
+                    PagerState(
+                        currentPage = it[0] as Int,
+                    )
+                }
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt
new file mode 100644
index 0000000..0b53f532
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/layout/pager/SnappingFlingBehavior.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.layout.pager
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.rememberSplineBasedDecay
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.LazyListItemInfo
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import kotlin.math.abs
+
+/** Default values used for [SnappingFlingBehavior] & [rememberSnappingFlingBehavior]. */
+internal object SnappingFlingBehaviorDefaults {
+    /** TODO */
+    val snapAnimationSpec: AnimationSpec<Float> = spring(stiffness = 600f)
+}
+
+/**
+ * Create and remember a snapping [FlingBehavior] to be used with [LazyListState].
+ *
+ * TODO: move this to a new module and make it public
+ *
+ * @param lazyListState The [LazyListState] to update.
+ * @param decayAnimationSpec The decay animation spec to use for decayed flings.
+ * @param snapAnimationSpec The animation spec to use when snapping.
+ */
+@Composable
+internal fun rememberSnappingFlingBehavior(
+    lazyListState: LazyListState,
+    decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
+    snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
+): SnappingFlingBehavior =
+    remember(lazyListState, decayAnimationSpec, snapAnimationSpec) {
+        SnappingFlingBehavior(
+            lazyListState = lazyListState,
+            decayAnimationSpec = decayAnimationSpec,
+            snapAnimationSpec = snapAnimationSpec,
+        )
+    }
+
+/**
+ * A snapping [FlingBehavior] for [LazyListState]. Typically this would be created via
+ * [rememberSnappingFlingBehavior].
+ *
+ * @param lazyListState The [LazyListState] to update.
+ * @param decayAnimationSpec The decay animation spec to use for decayed flings.
+ * @param snapAnimationSpec The animation spec to use when snapping.
+ */
+internal class SnappingFlingBehavior(
+    private val lazyListState: LazyListState,
+    private val decayAnimationSpec: DecayAnimationSpec<Float>,
+    private val snapAnimationSpec: AnimationSpec<Float>,
+) : FlingBehavior {
+    /** The target item index for any on-going animations. */
+    var animationTarget: Int? by mutableStateOf(null)
+        private set
+
+    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+        val itemInfo = currentItemInfo ?: return initialVelocity
+
+        // If the decay fling can scroll past the current item, fling with decay
+        return if (decayAnimationSpec.canFlingPastCurrentItem(itemInfo, initialVelocity)) {
+            performDecayFling(initialVelocity, itemInfo)
+        } else {
+            // Otherwise we 'spring' to current/next item
+            performSpringFling(
+                index =
+                    when {
+                        // If the velocity is greater than 1 item per second (velocity is px/s),
+                        // spring
+                        // in the relevant direction
+                        initialVelocity > itemInfo.size -> {
+                            (itemInfo.index + 1).coerceAtMost(
+                                lazyListState.layoutInfo.totalItemsCount - 1
+                            )
+                        }
+                        initialVelocity < -itemInfo.size -> itemInfo.index
+                        // If the velocity is 0 (or less than the size of the item), spring to
+                        // whichever item is closest to the snap point
+                        itemInfo.offset < -itemInfo.size / 2 -> itemInfo.index + 1
+                        else -> itemInfo.index
+                    },
+                initialVelocity = initialVelocity,
+            )
+        }
+    }
+
+    private suspend fun ScrollScope.performDecayFling(
+        initialVelocity: Float,
+        startItem: LazyListItemInfo,
+    ): Float {
+        val index =
+            when {
+                initialVelocity > 0 -> startItem.index + 1
+                else -> startItem.index
+            }
+        val forward = index > (currentItemInfo?.index ?: return initialVelocity)
+
+        // Update the animationTarget
+        animationTarget = index
+
+        var velocityLeft = initialVelocity
+        var lastValue = 0f
+        AnimationState(
+                initialValue = 0f,
+                initialVelocity = initialVelocity,
+            )
+            .animateDecay(decayAnimationSpec) {
+                val delta = value - lastValue
+                val consumed = scrollBy(delta)
+                lastValue = value
+                velocityLeft = this.velocity
+
+                val current = currentItemInfo
+                if (current == null) {
+                    cancelAnimation()
+                    return@animateDecay
+                }
+
+                if (
+                    !forward &&
+                        (current.index < index || current.index == index && current.offset >= 0)
+                ) {
+                    // 'snap back' to the item as we may have scrolled past it
+                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
+                    cancelAnimation()
+                } else if (
+                    forward &&
+                        (current.index > index || current.index == index && current.offset <= 0)
+                ) {
+                    // 'snap back' to the item as we may have scrolled past it
+                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
+                    cancelAnimation()
+                } else if (abs(delta - consumed) > 0.5f) {
+                    // avoid rounding errors and stop if anything is unconsumed
+                    cancelAnimation()
+                }
+            }
+        animationTarget = null
+        return velocityLeft
+    }
+
+    private suspend fun ScrollScope.performSpringFling(
+        index: Int,
+        scrollOffset: Int = 0,
+        initialVelocity: Float = 0f,
+    ): Float {
+        // If we don't have a current layout, we can't snap
+        val initialItem = currentItemInfo ?: return initialVelocity
+
+        val forward = index > initialItem.index
+        // We add 10% on to the size of the current item, to compensate for any item spacing, etc
+        val target = (if (forward) initialItem.size else -initialItem.size) * 1.1f
+
+        // Update the animationTarget
+        animationTarget = index
+
+        var velocityLeft = initialVelocity
+        var lastValue = 0f
+        AnimationState(
+                initialValue = 0f,
+                initialVelocity = initialVelocity,
+            )
+            .animateTo(
+                targetValue = target,
+                animationSpec = snapAnimationSpec,
+            ) {
+                // Springs can overshoot their target, clamp to the desired range
+                val coercedValue =
+                    if (forward) {
+                        value.coerceAtMost(target)
+                    } else {
+                        value.coerceAtLeast(target)
+                    }
+                val delta = coercedValue - lastValue
+                val consumed = scrollBy(delta)
+                lastValue = coercedValue
+                velocityLeft = this.velocity
+
+                val current = currentItemInfo
+                if (current == null) {
+                    cancelAnimation()
+                    return@animateTo
+                }
+
+                if (scrolledPastItem(initialVelocity, current, index, scrollOffset)) {
+                    // If we've scrolled to/past the item, stop the animation. We may also need to
+                    // 'snap back' to the item as we may have scrolled past it
+                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
+                    cancelAnimation()
+                } else if (abs(delta - consumed) > 0.5f) {
+                    // avoid rounding errors and stop if anything is unconsumed
+                    cancelAnimation()
+                }
+            }
+        animationTarget = null
+        return velocityLeft
+    }
+
+    private fun LazyListState.calculateScrollOffsetToItem(index: Int): Int {
+        return layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }?.offset ?: 0
+    }
+
+    private val currentItemInfo: LazyListItemInfo?
+        get() =
+            lazyListState.layoutInfo.visibleItemsInfo
+                .asSequence()
+                .filter { it.offset <= 0 && it.offset + it.size > 0 }
+                .lastOrNull()
+}
+
+private fun scrolledPastItem(
+    initialVelocity: Float,
+    currentItem: LazyListItemInfo,
+    targetIndex: Int,
+    targetScrollOffset: Int = 0,
+): Boolean {
+    return if (initialVelocity > 0) {
+        // forward
+        currentItem.index > targetIndex ||
+            (currentItem.index == targetIndex && currentItem.offset <= targetScrollOffset)
+    } else {
+        // backwards
+        currentItem.index < targetIndex ||
+            (currentItem.index == targetIndex && currentItem.offset >= targetScrollOffset)
+    }
+}
+
+private fun DecayAnimationSpec<Float>.canFlingPastCurrentItem(
+    currentItem: LazyListItemInfo,
+    initialVelocity: Float,
+): Boolean {
+    val targetValue =
+        calculateTargetValue(
+            initialValue = currentItem.offset.toFloat(),
+            initialVelocity = initialVelocity,
+        )
+    return when {
+        // forward. We add 10% onto the size to cater for any item spacing
+        initialVelocity > 0 -> targetValue <= -(currentItem.size * 1.1f)
+        // backwards. We add 10% onto the size to cater for any item spacing
+        else -> targetValue >= (currentItem.size * 0.1f)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt
new file mode 100644
index 0000000..3b13c0b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Padding.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.offset
+
+// This file was mostly copy/pasted from by androidx.compose.foundation.layout.Padding.kt and
+// contains modifiers with lambda parameters to change the padding of a Composable without
+// triggering recomposition when the paddings change.
+//
+// These should be used instead of the traditional size modifiers when the size changes often, for
+// instance when it is animated.
+//
+// TODO(b/247473910): Remove these modifiers once they can be fully replaced by layout animations
+// APIs.
+
+/** @see androidx.compose.foundation.layout.padding */
+fun Modifier.padding(
+    start: Density.() -> Int = PaddingUnspecified,
+    top: Density.() -> Int = PaddingUnspecified,
+    end: Density.() -> Int = PaddingUnspecified,
+    bottom: Density.() -> Int = PaddingUnspecified,
+) =
+    this.then(
+        PaddingModifier(
+            start,
+            top,
+            end,
+            bottom,
+            rtlAware = true,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "padding"
+                    properties["start"] = start
+                    properties["top"] = top
+                    properties["end"] = end
+                    properties["bottom"] = bottom
+                }
+        )
+    )
+
+/** @see androidx.compose.foundation.layout.padding */
+fun Modifier.padding(
+    horizontal: Density.() -> Int = PaddingUnspecified,
+    vertical: Density.() -> Int = PaddingUnspecified,
+): Modifier {
+    return this.then(
+        PaddingModifier(
+            start = horizontal,
+            top = vertical,
+            end = horizontal,
+            bottom = vertical,
+            rtlAware = true,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "padding"
+                    properties["horizontal"] = horizontal
+                    properties["vertical"] = vertical
+                }
+        )
+    )
+}
+
+private val PaddingUnspecified: Density.() -> Int = { 0 }
+
+private class PaddingModifier(
+    val start: Density.() -> Int,
+    val top: Density.() -> Int,
+    val end: Density.() -> Int,
+    val bottom: Density.() -> Int,
+    val rtlAware: Boolean,
+    inspectorInfo: InspectorInfo.() -> Unit
+) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val start = start()
+        val top = top()
+        val end = end()
+        val bottom = bottom()
+
+        val horizontal = start + end
+        val vertical = top + bottom
+
+        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
+
+        val width = constraints.constrainWidth(placeable.width + horizontal)
+        val height = constraints.constrainHeight(placeable.height + vertical)
+        return layout(width, height) {
+            if (rtlAware) {
+                placeable.placeRelative(start, top)
+            } else {
+                placeable.place(start, top)
+            }
+        }
+    }
+
+    override fun hashCode(): Int {
+        var result = start.hashCode()
+        result = 31 * result + top.hashCode()
+        result = 31 * result + end.hashCode()
+        result = 31 * result + bottom.hashCode()
+        result = 31 * result + rtlAware.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        val otherModifier = other as? PaddingModifier ?: return false
+        return start == otherModifier.start &&
+            top == otherModifier.top &&
+            end == otherModifier.end &&
+            bottom == otherModifier.bottom &&
+            rtlAware == otherModifier.rtlAware
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt
new file mode 100644
index 0000000..570d2431
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/Size.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.constrain
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+
+// This file was mostly copy pasted from androidx.compose.foundation.layout.Size.kt and contains
+// modifiers with lambda parameters to change the (min/max) size of a Composable without triggering
+// recomposition when the sizes change.
+//
+// These should be used instead of the traditional size modifiers when the size changes often, for
+// instance when it is animated.
+//
+// TODO(b/247473910): Remove these modifiers once they can be fully replaced by layout animations
+// APIs.
+
+/** @see androidx.compose.foundation.layout.width */
+fun Modifier.width(width: Density.() -> Int) =
+    this.then(
+        SizeModifier(
+            minWidth = width,
+            maxWidth = width,
+            enforceIncoming = true,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "width"
+                    value = width
+                }
+        )
+    )
+
+/** @see androidx.compose.foundation.layout.height */
+fun Modifier.height(height: Density.() -> Int) =
+    this.then(
+        SizeModifier(
+            minHeight = height,
+            maxHeight = height,
+            enforceIncoming = true,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "height"
+                    value = height
+                }
+        )
+    )
+
+/** @see androidx.compose.foundation.layout.size */
+fun Modifier.size(width: Density.() -> Int, height: Density.() -> Int) =
+    this.then(
+        SizeModifier(
+            minWidth = width,
+            maxWidth = width,
+            minHeight = height,
+            maxHeight = height,
+            enforceIncoming = true,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "size"
+                    properties["width"] = width
+                    properties["height"] = height
+                }
+        )
+    )
+
+private val SizeUnspecified: Density.() -> Int = { 0 }
+
+private class SizeModifier(
+    private val minWidth: Density.() -> Int = SizeUnspecified,
+    private val minHeight: Density.() -> Int = SizeUnspecified,
+    private val maxWidth: Density.() -> Int = SizeUnspecified,
+    private val maxHeight: Density.() -> Int = SizeUnspecified,
+    private val enforceIncoming: Boolean,
+    inspectorInfo: InspectorInfo.() -> Unit
+) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
+    private val Density.targetConstraints: Constraints
+        get() {
+            val maxWidth =
+                if (maxWidth != SizeUnspecified) {
+                    maxWidth().coerceAtLeast(0)
+                } else {
+                    Constraints.Infinity
+                }
+            val maxHeight =
+                if (maxHeight != SizeUnspecified) {
+                    maxHeight().coerceAtLeast(0)
+                } else {
+                    Constraints.Infinity
+                }
+            val minWidth =
+                if (minWidth != SizeUnspecified) {
+                    minWidth().coerceAtMost(maxWidth).coerceAtLeast(0).let {
+                        if (it != Constraints.Infinity) it else 0
+                    }
+                } else {
+                    0
+                }
+            val minHeight =
+                if (minHeight != SizeUnspecified) {
+                    minHeight().coerceAtMost(maxHeight).coerceAtLeast(0).let {
+                        if (it != Constraints.Infinity) it else 0
+                    }
+                } else {
+                    0
+                }
+            return Constraints(
+                minWidth = minWidth,
+                minHeight = minHeight,
+                maxWidth = maxWidth,
+                maxHeight = maxHeight
+            )
+        }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val wrappedConstraints =
+            targetConstraints.let { targetConstraints ->
+                if (enforceIncoming) {
+                    constraints.constrain(targetConstraints)
+                } else {
+                    val resolvedMinWidth =
+                        if (minWidth != SizeUnspecified) {
+                            targetConstraints.minWidth
+                        } else {
+                            constraints.minWidth.coerceAtMost(targetConstraints.maxWidth)
+                        }
+                    val resolvedMaxWidth =
+                        if (maxWidth != SizeUnspecified) {
+                            targetConstraints.maxWidth
+                        } else {
+                            constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth)
+                        }
+                    val resolvedMinHeight =
+                        if (minHeight != SizeUnspecified) {
+                            targetConstraints.minHeight
+                        } else {
+                            constraints.minHeight.coerceAtMost(targetConstraints.maxHeight)
+                        }
+                    val resolvedMaxHeight =
+                        if (maxHeight != SizeUnspecified) {
+                            targetConstraints.maxHeight
+                        } else {
+                            constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight)
+                        }
+                    Constraints(
+                        resolvedMinWidth,
+                        resolvedMaxWidth,
+                        resolvedMinHeight,
+                        resolvedMaxHeight
+                    )
+                }
+            }
+        val placeable = measurable.measure(wrappedConstraints)
+        return layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int {
+        val constraints = targetConstraints
+        return if (constraints.hasFixedWidth) {
+            constraints.maxWidth
+        } else {
+            constraints.constrainWidth(measurable.minIntrinsicWidth(height))
+        }
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int {
+        val constraints = targetConstraints
+        return if (constraints.hasFixedHeight) {
+            constraints.maxHeight
+        } else {
+            constraints.constrainHeight(measurable.minIntrinsicHeight(width))
+        }
+    }
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int {
+        val constraints = targetConstraints
+        return if (constraints.hasFixedWidth) {
+            constraints.maxWidth
+        } else {
+            constraints.constrainWidth(measurable.maxIntrinsicWidth(height))
+        }
+    }
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int {
+        val constraints = targetConstraints
+        return if (constraints.hasFixedHeight) {
+            constraints.maxHeight
+        } else {
+            constraints.constrainHeight(measurable.maxIntrinsicHeight(width))
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other !is SizeModifier) return false
+        return minWidth == other.minWidth &&
+            minHeight == other.minHeight &&
+            maxWidth == other.maxWidth &&
+            maxHeight == other.maxHeight &&
+            enforceIncoming == other.enforceIncoming
+    }
+
+    override fun hashCode() =
+        (((((minWidth.hashCode() * 31 + minHeight.hashCode()) * 31) + maxWidth.hashCode()) * 31) +
+            maxHeight.hashCode()) * 31
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
index 3175dcf..4d94bab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -17,8 +17,6 @@
 
 package com.android.systemui.user.ui.compose
 
-import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.graphics.drawable.Drawable
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.foundation.Image
@@ -50,10 +48,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -62,6 +58,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.compose.SysUiOutlinedButton
 import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@
         remember(viewModel.iconResourceId) {
             val drawable =
                 checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+            val size = with(density) { 20.dp.toPx() }.toInt()
             drawable
                 .toBitmap(
-                    size = with(density) { 20.dp.toPx() }.toInt(),
-                    tintColor = Color.White,
+                    width = size,
+                    height = size,
                 )
                 .asImageBitmap()
         }
@@ -392,32 +390,3 @@
                 ),
     )
 }
-
-/**
- * Converts the [Drawable] to a [Bitmap].
- *
- * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
- * the `Drawable` onto it. Use sparingly and with care.
- */
-private fun Drawable.toBitmap(
-    size: Int? = null,
-    tintColor: Color? = null,
-): Bitmap {
-    val bitmap =
-        if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
-            Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        } else {
-            Bitmap.createBitmap(
-                size ?: intrinsicWidth,
-                size ?: intrinsicHeight,
-                Bitmap.Config.ARGB_8888
-            )
-        }
-    val canvas = Canvas(bitmap)
-    setBounds(0, 0, canvas.width, canvas.height)
-    if (tintColor != null) {
-        setTint(tintColor.toArgb())
-    }
-    draw(canvas)
-    return bitmap
-}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
deleted file mode 100644
index 5a7a1e1..0000000
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ /dev/null
@@ -1,86 +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: "SystemUIComposeGalleryLib",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-        ":SystemUI-tests-utils",
-    ],
-
-    resource_dirs: [
-        "res",
-    ],
-
-    static_libs: [
-        "SystemUI-core",
-        "SystemUIComposeCore",
-        "SystemUIComposeFeatures",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "androidx.compose.material_material-icons-extended",
-        "androidx.activity_activity-compose",
-        "androidx.navigation_navigation-compose",
-
-        "androidx.appcompat_appcompat",
-
-        // TODO(b/240431193): Remove the dependencies and depend on
-        // SystemUI-test-utils directly.
-        "androidx.test.runner",
-        "mockito-target-extended-minus-junit4",
-        "testables",
-        "truth-prebuilt",
-        "androidx.test.uiautomator",
-        "kotlinx_coroutines_test",
-    ],
-
-    libs: [
-        "android.test.mock",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
-
-android_app {
-    name: "SystemUIComposeGallery",
-    defaults: ["platform_app_defaults"],
-    manifest: "app/AndroidManifest.xml",
-
-    static_libs: [
-        "SystemUIComposeGalleryLib",
-    ],
-
-    platform_apis: true,
-    system_ext_specific: true,
-    certificate: "platform",
-    privileged: true,
-
-    optimize: {
-        proguard_flags_files: ["proguard-rules.pro"],
-    },
-
-    dxflags: ["--multi-dex"],
-}
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
deleted file mode 100644
index 2f30651..0000000
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +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.compose.gallery">
-    <!-- To emulate a display size and density. -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
-    <application
-        android:name="android.app.Application"
-        android:appComponentFactory="androidx.core.app.AppComponentFactory"
-        tools:replace="android:name,android:appComponentFactory">
-        <!-- Disable providers from SystemUI -->
-        <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
-            android:authorities="com.android.systemui.test.keyguard.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
-        <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
-            android:authorities="com.android.systemui.test.keyguard.disabled"
-            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"
-            tools:replace="android:authorities"
-            tools:node="remove" />
-        <provider android:name="androidx.core.content.FileProvider"
-            android:authorities="com.android.systemui.test.fileprovider.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove"/>
-    </application>
-</manifest>
diff --git a/packages/SystemUI/compose/gallery/TEST_MAPPING b/packages/SystemUI/compose/gallery/TEST_MAPPING
deleted file mode 100644
index c7f8a92..0000000
--- a/packages/SystemUI/compose/gallery/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "SystemUIComposeGalleryTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
deleted file mode 100644
index 1f3fd8c..0000000
--- a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
+++ /dev/null
@@ -1,39 +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.compose.gallery.app">
-    <application
-        android:allowBackup="true"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
-        android:theme="@style/Theme.SystemUI.Gallery"
-        tools:replace="android:icon,android:theme,android:label">
-        <activity
-            android:name="com.android.systemui.compose.gallery.GalleryActivity"
-            android:exported="true"
-            android:label="@string/app_name">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/packages/SystemUI/compose/gallery/proguard-rules.pro b/packages/SystemUI/compose/gallery/proguard-rules.pro
deleted file mode 100644
index 481bb43..0000000
--- a/packages/SystemUI/compose/gallery/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg
deleted file mode 100644
index 6241b0b..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten1.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg
deleted file mode 100644
index 870ef13..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten2.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg
deleted file mode 100644
index bb7261c..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten3.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg
deleted file mode 100644
index e34b7dd..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten4.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg
deleted file mode 100644
index 9cde24b..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten5.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg b/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg
deleted file mode 100644
index 17825b6..0000000
--- a/packages/SystemUI/compose/gallery/res/drawable/kitten6.jpeg
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/compose/gallery/res/values/colors.xml b/packages/SystemUI/compose/gallery/res/values/colors.xml
deleted file mode 100644
index a2fcbff..0000000
--- a/packages/SystemUI/compose/gallery/res/values/colors.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.
--->
-<resources>
-    <color name="ic_launcher_background">#FFFFFF</color>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/values/strings.xml b/packages/SystemUI/compose/gallery/res/values/strings.xml
deleted file mode 100644
index 86bdb05..0000000
--- a/packages/SystemUI/compose/gallery/res/values/strings.xml
+++ /dev/null
@@ -1,20 +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>
-    <!-- Application name [CHAR LIMIT=NONE] -->
-    <string name="app_name">SystemUI Gallery</string>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml
deleted file mode 100644
index 45fa1f5d..0000000
--- a/packages/SystemUI/compose/gallery/res/values/themes.xml
+++ /dev/null
@@ -1,30 +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 xmlns:tools="http://schemas.android.com/tools">
-    <style name="Theme.SystemUI.Gallery">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-
-        <item name="android:statusBarColor" tools:targetApi="l">
-            @android:color/transparent
-        </item>
-        <item name="android:navigationBarColor" tools:targetApi="l">
-            @android:color/transparent
-        </item>
-        <item name="android:windowLightStatusBar">true</item>
-    </style>
-</resources>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt
deleted file mode 100644
index 881a1def..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt
+++ /dev/null
@@ -1,77 +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.
- *
- */
-
-@file:OptIn(ExperimentalMaterial3Api::class)
-
-package com.android.systemui.compose.gallery
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.compose.SysUiButton
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-
-@Composable
-fun ButtonsScreen(
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        modifier = modifier,
-    ) {
-        SysUiButton(
-            onClick = {},
-        ) {
-            Text("SysUiButton")
-        }
-
-        SysUiButton(
-            onClick = {},
-            enabled = false,
-        ) {
-            Text("SysUiButton - disabled")
-        }
-
-        SysUiOutlinedButton(
-            onClick = {},
-        ) {
-            Text("SysUiOutlinedButton")
-        }
-
-        SysUiOutlinedButton(
-            onClick = {},
-            enabled = false,
-        ) {
-            Text("SysUiOutlinedButton - disabled")
-        }
-
-        SysUiTextButton(
-            onClick = {},
-        ) {
-            Text("SysUiTextButton")
-        }
-
-        SysUiTextButton(
-            onClick = {},
-            enabled = false,
-        ) {
-            Text("SysUiTextButton - disabled")
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt
deleted file mode 100644
index dfa1b26..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt
+++ /dev/null
@@ -1,139 +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.compose.gallery
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-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.graphics.Color
-import androidx.compose.ui.unit.dp
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-
-/** The screen that shows all the Material 3 colors. */
-@Composable
-fun MaterialColorsScreen() {
-    val colors = MaterialTheme.colorScheme
-    ColorsScreen(
-        listOf(
-            "primary" to colors.primary,
-            "onPrimary" to colors.onPrimary,
-            "primaryContainer" to colors.primaryContainer,
-            "onPrimaryContainer" to colors.onPrimaryContainer,
-            "inversePrimary" to colors.inversePrimary,
-            "secondary" to colors.secondary,
-            "onSecondary" to colors.onSecondary,
-            "secondaryContainer" to colors.secondaryContainer,
-            "onSecondaryContainer" to colors.onSecondaryContainer,
-            "tertiary" to colors.tertiary,
-            "onTertiary" to colors.onTertiary,
-            "tertiaryContainer" to colors.tertiaryContainer,
-            "onTertiaryContainer" to colors.onTertiaryContainer,
-            "background" to colors.background,
-            "onBackground" to colors.onBackground,
-            "surface" to colors.surface,
-            "onSurface" to colors.onSurface,
-            "surfaceVariant" to colors.surfaceVariant,
-            "onSurfaceVariant" to colors.onSurfaceVariant,
-            "inverseSurface" to colors.inverseSurface,
-            "inverseOnSurface" to colors.inverseOnSurface,
-            "error" to colors.error,
-            "onError" to colors.onError,
-            "errorContainer" to colors.errorContainer,
-            "onErrorContainer" to colors.onErrorContainer,
-            "outline" to colors.outline,
-        )
-    )
-}
-
-/** The screen that shows all the Android colors. */
-@Composable
-fun AndroidColorsScreen() {
-    val colors = LocalAndroidColorScheme.current
-    ColorsScreen(
-        listOf(
-            "colorPrimary" to colors.colorPrimary,
-            "colorPrimaryDark" to colors.colorPrimaryDark,
-            "colorAccent" to colors.colorAccent,
-            "colorAccentPrimary" to colors.colorAccentPrimary,
-            "colorAccentSecondary" to colors.colorAccentSecondary,
-            "colorAccentTertiary" to colors.colorAccentTertiary,
-            "colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant,
-            "colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant,
-            "colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant,
-            "colorSurface" to colors.colorSurface,
-            "colorSurfaceHighlight" to colors.colorSurfaceHighlight,
-            "colorSurfaceVariant" to colors.colorSurfaceVariant,
-            "colorSurfaceHeader" to colors.colorSurfaceHeader,
-            "colorError" to colors.colorError,
-            "colorBackground" to colors.colorBackground,
-            "colorBackgroundFloating" to colors.colorBackgroundFloating,
-            "panelColorBackground" to colors.panelColorBackground,
-            "textColorPrimary" to colors.textColorPrimary,
-            "textColorSecondary" to colors.textColorSecondary,
-            "textColorTertiary" to colors.textColorTertiary,
-            "textColorPrimaryInverse" to colors.textColorPrimaryInverse,
-            "textColorSecondaryInverse" to colors.textColorSecondaryInverse,
-            "textColorTertiaryInverse" to colors.textColorTertiaryInverse,
-            "textColorOnAccent" to colors.textColorOnAccent,
-            "colorForeground" to colors.colorForeground,
-            "colorForegroundInverse" to colors.colorForegroundInverse,
-        )
-    )
-}
-
-@Composable
-private fun ColorsScreen(
-    colors: List<Pair<String, Color>>,
-) {
-    LazyColumn(
-        Modifier.fillMaxWidth(),
-    ) {
-        colors.forEach { (name, color) -> item { ColorTile(color, name) } }
-    }
-}
-
-@Composable
-private fun ColorTile(
-    color: Color,
-    name: String,
-) {
-    Row(
-        Modifier.padding(16.dp),
-        verticalAlignment = Alignment.CenterVertically,
-    ) {
-        val shape = RoundedCornerShape(16.dp)
-        Spacer(
-            Modifier.border(1.dp, MaterialTheme.colorScheme.onBackground, shape)
-                .background(color, shape)
-                .size(64.dp)
-        )
-        Spacer(Modifier.width(16.dp))
-        Text(name)
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
deleted file mode 100644
index 990d060..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-package com.android.systemui.compose.gallery
-
-import android.graphics.Point
-import android.os.UserHandle
-import android.view.Display
-import android.view.WindowManagerGlobal
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.DarkMode
-import androidx.compose.material.icons.filled.FormatSize
-import androidx.compose.material.icons.filled.FormatTextdirectionLToR
-import androidx.compose.material.icons.filled.FormatTextdirectionRToL
-import androidx.compose.material.icons.filled.InvertColors
-import androidx.compose.material.icons.filled.LightMode
-import androidx.compose.material.icons.filled.Smartphone
-import androidx.compose.material.icons.filled.Tablet
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import kotlin.math.max
-import kotlin.math.min
-
-enum class FontScale(val scale: Float) {
-    Small(0.85f),
-    Normal(1f),
-    Big(1.15f),
-    Bigger(1.30f),
-}
-
-/** A configuration panel that allows to toggle the theme, font scale and layout direction. */
-@Composable
-fun ConfigurationControls(
-    theme: Theme,
-    fontScale: FontScale,
-    layoutDirection: LayoutDirection,
-    onChangeTheme: () -> Unit,
-    onChangeLayoutDirection: () -> Unit,
-    onChangeFontScale: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    // The display we are emulating, if any.
-    var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) }
-    val emulatedDisplay =
-        emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } }
-
-    LaunchedEffect(emulatedDisplay) {
-        val wm = WindowManagerGlobal.getWindowManagerService()
-
-        val defaultDisplayId = Display.DEFAULT_DISPLAY
-        if (emulatedDisplay == null) {
-            wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId())
-            wm.clearForcedDisplaySize(defaultDisplayId)
-        } else {
-            val density = emulatedDisplay.densityDpi
-
-            // Emulate the display and make sure that we use the maximum available space possible.
-            val initialSize = Point()
-            wm.getInitialDisplaySize(defaultDisplayId, initialSize)
-            val width = emulatedDisplay.width
-            val height = emulatedDisplay.height
-            val minOfSize = min(width, height)
-            val maxOfSize = max(width, height)
-            if (initialSize.x < initialSize.y) {
-                wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize)
-            } else {
-                wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize)
-            }
-            wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId())
-        }
-    }
-
-    // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
-    // don't miss any available configuration.
-    LazyRow(modifier) {
-        // Dark/light theme.
-        item {
-            TextButton(onChangeTheme) {
-                val text: String
-                val icon: ImageVector
-
-                when (theme) {
-                    Theme.System -> {
-                        icon = Icons.Default.InvertColors
-                        text = "System"
-                    }
-                    Theme.Dark -> {
-                        icon = Icons.Default.DarkMode
-                        text = "Dark"
-                    }
-                    Theme.Light -> {
-                        icon = Icons.Default.LightMode
-                        text = "Light"
-                    }
-                }
-
-                Icon(icon, null)
-                Spacer(Modifier.width(8.dp))
-                Text(text)
-            }
-        }
-
-        // Font scale.
-        item {
-            TextButton(onChangeFontScale) {
-                Icon(Icons.Default.FormatSize, null)
-                Spacer(Modifier.width(8.dp))
-
-                Text(fontScale.name)
-            }
-        }
-
-        // Layout direction.
-        item {
-            TextButton(onChangeLayoutDirection) {
-                when (layoutDirection) {
-                    LayoutDirection.Ltr -> {
-                        Icon(Icons.Default.FormatTextdirectionLToR, null)
-                        Spacer(Modifier.width(8.dp))
-                        Text("LTR")
-                    }
-                    LayoutDirection.Rtl -> {
-                        Icon(Icons.Default.FormatTextdirectionRToL, null)
-                        Spacer(Modifier.width(8.dp))
-                        Text("RTL")
-                    }
-                }
-            }
-        }
-
-        // Display emulation.
-        EmulatedDisplays.forEach { display ->
-            item {
-                DisplayButton(
-                    display,
-                    emulatedDisplay == display,
-                    { emulatedDisplayName = it?.name },
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun DisplayButton(
-    display: EmulatedDisplay,
-    selected: Boolean,
-    onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit,
-) {
-    val onClick = {
-        if (selected) {
-            onChangeEmulatedDisplay(null)
-        } else {
-            onChangeEmulatedDisplay(display)
-        }
-    }
-
-    val content: @Composable RowScope.() -> Unit = {
-        Icon(display.icon, null)
-        Spacer(Modifier.width(8.dp))
-        Text(display.name)
-    }
-
-    if (selected) {
-        Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content)
-    } else {
-        TextButton(onClick, content = content)
-    }
-}
-
-/** The displays that can be emulated from this Gallery app. */
-private val EmulatedDisplays =
-    listOf(
-        EmulatedDisplay(
-            "Phone",
-            Icons.Default.Smartphone,
-            width = 1440,
-            height = 3120,
-            densityDpi = 560,
-        ),
-        EmulatedDisplay(
-            "Tablet",
-            Icons.Default.Tablet,
-            width = 2560,
-            height = 1600,
-            densityDpi = 320,
-        ),
-    )
-
-private data class EmulatedDisplay(
-    val name: String,
-    val icon: ImageVector,
-    val width: Int,
-    val height: Int,
-    val densityDpi: Int,
-)
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
deleted file mode 100644
index 6e17214..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
+++ /dev/null
@@ -1,28 +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.compose.gallery
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.ExampleFeature
-
-/** The screen that shows ExampleFeature. */
-@Composable
-fun ExampleFeatureScreen(modifier: Modifier = Modifier) {
-    Column(modifier) { ExampleFeature("This is an example feature!") }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
deleted file mode 100644
index bb2d2fe..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
+++ /dev/null
@@ -1,80 +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.compose.gallery
-
-import android.app.UiModeManager
-import android.content.Context
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.graphics.Color
-import androidx.core.view.WindowCompat
-import com.android.systemui.compose.rememberSystemUiController
-
-class GalleryActivity : ComponentActivity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        WindowCompat.setDecorFitsSystemWindows(window, false)
-        val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
-
-        setContent {
-            var theme by rememberSaveable { mutableStateOf(Theme.System) }
-            val onChangeTheme = {
-                // Change to the next theme for a toggle behavior.
-                theme =
-                    when (theme) {
-                        Theme.System -> Theme.Dark
-                        Theme.Dark -> Theme.Light
-                        Theme.Light -> Theme.System
-                    }
-            }
-
-            val isSystemInDarkTheme = isSystemInDarkTheme()
-            val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme)
-            val useDarkIcons = !isDark
-            val systemUiController = rememberSystemUiController()
-            SideEffect {
-                systemUiController.setSystemBarsColor(
-                    color = Color.Transparent,
-                    darkIcons = useDarkIcons,
-                )
-
-                uiModeManager.setApplicationNightMode(
-                    when (theme) {
-                        Theme.System -> UiModeManager.MODE_NIGHT_AUTO
-                        Theme.Dark -> UiModeManager.MODE_NIGHT_YES
-                        Theme.Light -> UiModeManager.MODE_NIGHT_NO
-                    }
-                )
-            }
-
-            GalleryApp(theme, onChangeTheme)
-        }
-    }
-}
-
-enum class Theme {
-    System,
-    Dark,
-    Light,
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
deleted file mode 100644
index 6805bf8..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.android.systemui.compose.gallery
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.systemBarsPadding
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.rememberNavController
-import com.android.systemui.compose.theme.SystemUITheme
-
-/** The gallery app screens. */
-object GalleryAppScreens {
-    private val Typography = ChildScreen("typography") { TypographyScreen() }
-    private val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() }
-    private val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
-    private val Buttons = ChildScreen("buttons") { ButtonsScreen() }
-    private val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
-
-    private val PeopleEmpty =
-        ChildScreen("people_empty") { navController ->
-            EmptyPeopleScreen(onResult = { navController.popBackStack() })
-        }
-    private val PeopleFew =
-        ChildScreen("people_few") { navController ->
-            FewPeopleScreen(onResult = { navController.popBackStack() })
-        }
-    private val PeopleFull =
-        ChildScreen("people_full") { navController ->
-            FullPeopleScreen(onResult = { navController.popBackStack() })
-        }
-    private val People =
-        ParentScreen(
-            "people",
-            mapOf(
-                "Empty" to PeopleEmpty,
-                "Few" to PeopleFew,
-                "Full" to PeopleFull,
-            )
-        )
-    private val UserSwitcherSingleUser =
-        ChildScreen("user_switcher_single") { navController ->
-            UserSwitcherScreen(
-                userCount = 1,
-                onFinished = navController::popBackStack,
-            )
-        }
-    private val UserSwitcherThreeUsers =
-        ChildScreen("user_switcher_three") { navController ->
-            UserSwitcherScreen(
-                userCount = 3,
-                onFinished = navController::popBackStack,
-            )
-        }
-    private val UserSwitcherFourUsers =
-        ChildScreen("user_switcher_four") { navController ->
-            UserSwitcherScreen(
-                userCount = 4,
-                onFinished = navController::popBackStack,
-            )
-        }
-    private val UserSwitcherFiveUsers =
-        ChildScreen("user_switcher_five") { navController ->
-            UserSwitcherScreen(
-                userCount = 5,
-                onFinished = navController::popBackStack,
-            )
-        }
-    private val UserSwitcherSixUsers =
-        ChildScreen("user_switcher_six") { navController ->
-            UserSwitcherScreen(
-                userCount = 6,
-                onFinished = navController::popBackStack,
-            )
-        }
-    private val UserSwitcher =
-        ParentScreen(
-            "user_switcher",
-            mapOf(
-                "Single" to UserSwitcherSingleUser,
-                "Three" to UserSwitcherThreeUsers,
-                "Four" to UserSwitcherFourUsers,
-                "Five" to UserSwitcherFiveUsers,
-                "Six" to UserSwitcherSixUsers,
-            )
-        )
-
-    val Home =
-        ParentScreen(
-            "home",
-            mapOf(
-                "Typography" to Typography,
-                "Material colors" to MaterialColors,
-                "Android colors" to AndroidColors,
-                "Example feature" to ExampleFeature,
-                "Buttons" to Buttons,
-                "People" to People,
-                "User Switcher" to UserSwitcher,
-            )
-        )
-}
-
-/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
-@Composable
-private fun MainContent(onControlToggleRequested: () -> Unit) {
-    Box(Modifier.fillMaxSize()) {
-        val navController = rememberNavController()
-        NavHost(
-            navController = navController,
-            startDestination = GalleryAppScreens.Home.identifier,
-        ) {
-            screen(GalleryAppScreens.Home, navController, onControlToggleRequested)
-        }
-    }
-}
-
-/**
- * The top-level composable shown when starting the app. This composable always shows a
- * [ConfigurationControls] at the top of the screen, above the [MainContent].
- */
-@Composable
-fun GalleryApp(
-    theme: Theme,
-    onChangeTheme: () -> Unit,
-) {
-    val systemFontScale = LocalDensity.current.fontScale
-    var fontScale: FontScale by rememberSaveable {
-        mutableStateOf(
-            FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal
-        )
-    }
-    val context = LocalContext.current
-    val density = Density(context.resources.displayMetrics.density, fontScale.scale)
-    val onChangeFontScale = {
-        fontScale =
-            when (fontScale) {
-                FontScale.Small -> FontScale.Normal
-                FontScale.Normal -> FontScale.Big
-                FontScale.Big -> FontScale.Bigger
-                FontScale.Bigger -> FontScale.Small
-            }
-    }
-
-    val systemLayoutDirection = LocalLayoutDirection.current
-    var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) }
-    val onChangeLayoutDirection = {
-        layoutDirection =
-            when (layoutDirection) {
-                LayoutDirection.Ltr -> LayoutDirection.Rtl
-                LayoutDirection.Rtl -> LayoutDirection.Ltr
-            }
-    }
-
-    CompositionLocalProvider(
-        LocalDensity provides density,
-        LocalLayoutDirection provides layoutDirection,
-    ) {
-        SystemUITheme {
-            Surface(
-                Modifier.fillMaxSize(),
-                color = MaterialTheme.colorScheme.background,
-            ) {
-                Column(Modifier.fillMaxSize().systemBarsPadding()) {
-                    var showControls by rememberSaveable { mutableStateOf(true) }
-
-                    if (showControls) {
-                        ConfigurationControls(
-                            theme,
-                            fontScale,
-                            layoutDirection,
-                            onChangeTheme,
-                            onChangeLayoutDirection,
-                            onChangeFontScale,
-                            Modifier.padding(horizontal = 16.dp),
-                        )
-
-                        Spacer(Modifier.height(4.dp))
-                    }
-
-                    MainContent(onControlToggleRequested = { showControls = !showControls })
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
deleted file mode 100644
index 2f0df77..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
+++ /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.systemui.compose.gallery
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
-import com.android.systemui.people.emptyPeopleSpaceViewModel
-import com.android.systemui.people.fewPeopleSpaceViewModel
-import com.android.systemui.people.fullPeopleSpaceViewModel
-import com.android.systemui.people.ui.compose.PeopleScreen
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-
-@Composable
-fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
-    val context = LocalContext.current.applicationContext
-    val viewModel = emptyPeopleSpaceViewModel(context)
-    PeopleScreen(viewModel, onResult)
-}
-
-@Composable
-fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
-    val context = LocalContext.current.applicationContext
-    val viewModel = fewPeopleSpaceViewModel(context)
-    PeopleScreen(viewModel, onResult)
-}
-
-@Composable
-fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
-    val context = LocalContext.current.applicationContext
-    val viewModel = fullPeopleSpaceViewModel(context)
-    PeopleScreen(viewModel, onResult)
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
deleted file mode 100644
index d7d0d72..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
+++ /dev/null
@@ -1,126 +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.compose.gallery
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigation
-
-/**
- * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to
- * them or a [ChildScreen] which shows some content.
- */
-sealed class Screen(val identifier: String)
-
-class ParentScreen(
-    identifier: String,
-    val children: Map<String, Screen>,
-) : Screen(identifier)
-
-class ChildScreen(
-    identifier: String,
-    val content: @Composable (NavController) -> Unit,
-) : Screen(identifier)
-
-/** Create the navigation graph for [screen]. */
-fun NavGraphBuilder.screen(
-    screen: Screen,
-    navController: NavController,
-    onControlToggleRequested: () -> Unit,
-) {
-    when (screen) {
-        is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
-        is ParentScreen -> {
-            val menuRoute = "${screen.identifier}_menu"
-            navigation(startDestination = menuRoute, route = screen.identifier) {
-                // The menu to navigate to one of the children screens.
-                composable(menuRoute) {
-                    ScreenMenu(screen, navController, onControlToggleRequested)
-                }
-
-                // The content of the child screens.
-                screen.children.forEach { (_, child) ->
-                    screen(
-                        child,
-                        navController,
-                        onControlToggleRequested,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun ScreenMenu(
-    screen: ParentScreen,
-    navController: NavController,
-    onControlToggleRequested: () -> Unit,
-) {
-    LazyColumn(
-        Modifier.padding(horizontal = 16.dp),
-        verticalArrangement = Arrangement.spacedBy(8.dp),
-    ) {
-        item {
-            Surface(
-                Modifier.fillMaxWidth(),
-                color = MaterialTheme.colorScheme.tertiaryContainer,
-                shape = CircleShape,
-            ) {
-                Column(
-                    Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp),
-                    horizontalAlignment = Alignment.CenterHorizontally,
-                ) {
-                    Text("Toggle controls")
-                }
-            }
-        }
-
-        screen.children.forEach { (name, child) ->
-            item {
-                Surface(
-                    Modifier.fillMaxWidth(),
-                    color = MaterialTheme.colorScheme.secondaryContainer,
-                    shape = CircleShape,
-                ) {
-                    Column(
-                        Modifier.clickable { navController.navigate(child.identifier) }
-                            .padding(16.dp),
-                        horizontalAlignment = Alignment.CenterHorizontally,
-                    ) {
-                        Text(name)
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt
deleted file mode 100644
index 147025e..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt
+++ /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.systemui.compose.gallery
-
-import androidx.compose.foundation.horizontalScroll
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextOverflow
-
-/** The screen that shows the Material text styles. */
-@Composable
-fun TypographyScreen() {
-    val typography = MaterialTheme.typography
-
-    Column(
-        Modifier.fillMaxSize()
-            .horizontalScroll(rememberScrollState())
-            .verticalScroll(rememberScrollState()),
-    ) {
-        FontLine("displayLarge", typography.displayLarge)
-        FontLine("displayMedium", typography.displayMedium)
-        FontLine("displaySmall", typography.displaySmall)
-        FontLine("headlineLarge", typography.headlineLarge)
-        FontLine("headlineMedium", typography.headlineMedium)
-        FontLine("headlineSmall", typography.headlineSmall)
-        FontLine("titleLarge", typography.titleLarge)
-        FontLine("titleMedium", typography.titleMedium)
-        FontLine("titleSmall", typography.titleSmall)
-        FontLine("bodyLarge", typography.bodyLarge)
-        FontLine("bodyMedium", typography.bodyMedium)
-        FontLine("bodySmall", typography.bodySmall)
-        FontLine("labelLarge", typography.labelLarge)
-        FontLine("labelMedium", typography.labelMedium)
-        FontLine("labelSmall", typography.labelSmall)
-    }
-}
-
-@Composable
-private fun FontLine(name: String, style: TextStyle) {
-    Text(
-        "$name (${style.fontSize}/${style.lineHeight}, W${style.fontWeight?.weight})",
-        style = style,
-        maxLines = 1,
-        overflow = TextOverflow.Visible,
-    )
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt
deleted file mode 100644
index fe9707d..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,35 +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.compose.gallery
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
-import com.android.systemui.user.Fakes.fakeUserSwitcherViewModel
-import com.android.systemui.user.ui.compose.UserSwitcherScreen
-
-@Composable
-fun UserSwitcherScreen(
-    userCount: Int,
-    onFinished: () -> Unit,
-) {
-    val context = LocalContext.current.applicationContext
-    UserSwitcherScreen(
-        viewModel = fakeUserSwitcherViewModel(context, userCount = userCount),
-        onFinished = onFinished,
-    )
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
deleted file mode 100644
index 0966c32..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
+++ /dev/null
@@ -1,156 +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.people
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.drawable.Icon
-import androidx.core.graphics.drawable.toIcon
-import com.android.systemui.R
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.people.data.model.PeopleTileModel
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.people.widget.PeopleTileKey
-
-/** A [PeopleViewModel] that does not have any conversations. */
-fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
-    return fakePeopleSpaceViewModel(context, emptyList(), emptyList())
-}
-
-/** A [PeopleViewModel] that has a few conversations. */
-fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
-    return fakePeopleSpaceViewModel(
-        context,
-        priorityTiles =
-            listOf(
-                fakeTile(context, id = "0", Color.RED, "Priority"),
-                fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
-            ),
-        recentTiles =
-            listOf(
-                fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true),
-                fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true),
-            ),
-    )
-}
-
-/** A [PeopleViewModel] that has a lot of conversations. */
-fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
-    return fakePeopleSpaceViewModel(
-        context,
-        priorityTiles =
-            listOf(
-                fakeTile(context, id = "0", Color.RED, "Priority"),
-                fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
-                fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true),
-                fakeTile(
-                    context,
-                    id = "3",
-                    Color.CYAN,
-                    "Priority DndBlocking",
-                    isDndBlocking = true,
-                ),
-                fakeTile(
-                    context,
-                    id = "4",
-                    Color.MAGENTA,
-                    "Priority NewStory Important",
-                    hasNewStory = true,
-                    isImportant = true,
-                ),
-            ),
-        recentTiles =
-            listOf(
-                fakeTile(
-                    context,
-                    id = "5",
-                    Color.RED,
-                    "Recent NewStory DndBlocking",
-                    hasNewStory = true,
-                    isDndBlocking = true,
-                ),
-                fakeTile(
-                    context,
-                    id = "6",
-                    Color.BLUE,
-                    "Recent Important DndBlocking",
-                    isImportant = true,
-                    isDndBlocking = true,
-                ),
-                fakeTile(
-                    context,
-                    id = "7",
-                    Color.GREEN,
-                    "Recent NewStory Important DndBlocking",
-                    hasNewStory = true,
-                    isImportant = true,
-                    isDndBlocking = true,
-                ),
-                fakeTile(context, id = "8", Color.CYAN, "Recent"),
-                fakeTile(context, id = "9", Color.MAGENTA, "Recent"),
-            ),
-    )
-}
-
-private fun fakePeopleSpaceViewModel(
-    @Application context: Context,
-    priorityTiles: List<PeopleTileModel>,
-    recentTiles: List<PeopleTileModel>,
-): PeopleViewModel {
-    return PeopleViewModel(
-        context,
-        FakePeopleTileRepository(priorityTiles, recentTiles),
-        FakePeopleWidgetRepository(),
-    )
-}
-
-private fun fakeTile(
-    @Application context: Context,
-    id: String,
-    iconColor: Int,
-    username: String,
-    hasNewStory: Boolean = false,
-    isImportant: Boolean = false,
-    isDndBlocking: Boolean = false
-): PeopleTileModel {
-    return PeopleTileModel(
-        PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""),
-        username,
-        fakeUserIcon(context, iconColor),
-        hasNewStory,
-        isImportant,
-        isDndBlocking,
-    )
-}
-
-private fun fakeUserIcon(@Application context: Context, color: Int): Icon {
-    val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium)
-    val bitmap =
-        Bitmap.createBitmap(
-            size,
-            size,
-            Bitmap.Config.ARGB_8888,
-        )
-    val canvas = Canvas(bitmap)
-    val paint = Paint().apply { this.color = color }
-    val radius = size / 2f
-    canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint)
-    return bitmap.toIcon()
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt
deleted file mode 100644
index 6588e22..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt
+++ /dev/null
@@ -1,164 +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.qs.footer
-
-import android.content.Context
-import android.os.UserHandle
-import android.view.View
-import com.android.internal.util.UserIcons
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
-import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
-import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
-
-/** A list of fake [FooterActionsViewModel] to be used in screenshot tests and the gallery. */
-fun fakeFooterActionsViewModels(
-    @Application context: Context,
-): List<FooterActionsViewModel> {
-    return listOf(
-        fakeFooterActionsViewModel(context),
-        fakeFooterActionsViewModel(context, showPowerButton = false, isGuestUser = true),
-        fakeFooterActionsViewModel(context, showUserSwitcher = false),
-        fakeFooterActionsViewModel(context, showUserSwitcher = false, foregroundServices = 4),
-        fakeFooterActionsViewModel(
-            context,
-            foregroundServices = 4,
-            hasNewForegroundServices = true,
-            userId = 1,
-        ),
-        fakeFooterActionsViewModel(
-            context,
-            securityText = "Security",
-            foregroundServices = 4,
-            showUserSwitcher = false,
-        ),
-        fakeFooterActionsViewModel(
-            context,
-            securityText = "Security (not clickable)",
-            securityClickable = false,
-            foregroundServices = 4,
-            hasNewForegroundServices = true,
-            userId = 2,
-        ),
-    )
-}
-
-private fun fakeFooterActionsViewModel(
-    @Application context: Context,
-    securityText: String? = null,
-    securityClickable: Boolean = true,
-    foregroundServices: Int = 0,
-    hasNewForegroundServices: Boolean = false,
-    showUserSwitcher: Boolean = true,
-    showPowerButton: Boolean = true,
-    userId: Int = UserHandle.USER_OWNER,
-    isGuestUser: Boolean = false,
-): FooterActionsViewModel {
-    val interactor =
-        FakeFooterActionsInteractor(
-            securityButtonConfig =
-                flowOf(
-                    securityText?.let { text ->
-                        SecurityButtonConfig(
-                            icon =
-                                Icon.Resource(
-                                    R.drawable.ic_info_outline,
-                                    contentDescription = null,
-                                ),
-                            text = text,
-                            isClickable = securityClickable,
-                        )
-                    }
-                ),
-            foregroundServicesCount = flowOf(foregroundServices),
-            hasNewForegroundServices = flowOf(hasNewForegroundServices),
-            userSwitcherStatus =
-                flowOf(
-                    if (showUserSwitcher) {
-                        UserSwitcherStatusModel.Enabled(
-                            currentUserName = "foo",
-                            currentUserImage =
-                                UserIcons.getDefaultUserIcon(
-                                    context.resources,
-                                    userId,
-                                    /* light= */ false,
-                                ),
-                            isGuestUser = isGuestUser,
-                        )
-                    } else {
-                        UserSwitcherStatusModel.Disabled
-                    }
-                ),
-            deviceMonitoringDialogRequests = flowOf(),
-        )
-
-    return FooterActionsViewModel(
-        context,
-        interactor,
-        FalsingManagerFake(),
-        globalActionsDialogLite = mock(),
-        showPowerButton = showPowerButton,
-    )
-}
-
-private class FakeFooterActionsInteractor(
-    override val securityButtonConfig: Flow<SecurityButtonConfig?> = flowOf(null),
-    override val foregroundServicesCount: Flow<Int> = flowOf(0),
-    override val hasNewForegroundServices: Flow<Boolean> = flowOf(false),
-    override val userSwitcherStatus: Flow<UserSwitcherStatusModel> =
-        flowOf(UserSwitcherStatusModel.Disabled),
-    override val deviceMonitoringDialogRequests: Flow<Unit> = flowOf(),
-    private val onShowDeviceMonitoringDialogFromView: (View) -> Unit = {},
-    private val onShowDeviceMonitoringDialog: (Context) -> Unit = {},
-    private val onShowForegroundServicesDialog: (View) -> Unit = {},
-    private val onShowPowerMenuDialog: (GlobalActionsDialogLite, View) -> Unit = { _, _ -> },
-    private val onShowSettings: (Expandable) -> Unit = {},
-    private val onShowUserSwitcher: (View) -> Unit = {},
-) : FooterActionsInteractor {
-    override fun showDeviceMonitoringDialog(view: View) {
-        onShowDeviceMonitoringDialogFromView(view)
-    }
-
-    override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
-        onShowDeviceMonitoringDialog(quickSettingsContext)
-    }
-
-    override fun showForegroundServicesDialog(view: View) {
-        onShowForegroundServicesDialog(view)
-    }
-
-    override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
-        onShowPowerMenuDialog(globalActionsDialogLite, view)
-    }
-
-    override fun showSettings(expandable: Expandable) {
-        onShowSettings(expandable)
-    }
-
-    override fun showUserSwitcher(view: View) {
-        onShowUserSwitcher(view)
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt
deleted file mode 100644
index 91a73ea..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/user/Fakes.kt
+++ /dev/null
@@ -1,116 +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.user
-
-import android.content.Context
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.compose.gallery.R
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.util.mockito.mock
-
-object Fakes {
-    private val USER_TINT_COLORS =
-        arrayOf(
-            0x000000,
-            0x0000ff,
-            0x00ff00,
-            0x00ffff,
-            0xff0000,
-            0xff00ff,
-            0xffff00,
-            0xffffff,
-        )
-
-    fun fakeUserSwitcherViewModel(
-        context: Context,
-        userCount: Int,
-    ): UserSwitcherViewModel {
-        return UserSwitcherViewModel.Factory(
-                userInteractor =
-                    UserInteractor(
-                        repository =
-                            FakeUserRepository().apply {
-                                setUsers(
-                                    (0 until userCount).map { index ->
-                                        UserModel(
-                                            id = index,
-                                            name =
-                                                Text.Loaded(
-                                                    when (index % 6) {
-                                                        0 -> "Ross Geller"
-                                                        1 -> "Phoebe Buffay"
-                                                        2 -> "Monica Geller"
-                                                        3 -> "Rachel Greene"
-                                                        4 -> "Chandler Bing"
-                                                        else -> "Joey Tribbiani"
-                                                    }
-                                                ),
-                                            image =
-                                                checkNotNull(
-                                                    AppCompatResources.getDrawable(
-                                                        context,
-                                                        when (index % 6) {
-                                                            0 -> R.drawable.kitten1
-                                                            1 -> R.drawable.kitten2
-                                                            2 -> R.drawable.kitten3
-                                                            3 -> R.drawable.kitten4
-                                                            4 -> R.drawable.kitten5
-                                                            else -> R.drawable.kitten6
-                                                        },
-                                                    )
-                                                ),
-                                            isSelected = index == 0,
-                                            isSelectable = true,
-                                        )
-                                    }
-                                )
-                                setActions(
-                                    UserActionModel.values().mapNotNull {
-                                        if (it == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
-                                            null
-                                        } else {
-                                            it
-                                        }
-                                    }
-                                )
-                            },
-                        controller = mock(),
-                        activityStarter = mock(),
-                        keyguardInteractor =
-                            KeyguardInteractor(
-                                repository =
-                                    FakeKeyguardRepository().apply { setKeyguardShowing(false) },
-                            ),
-                    ),
-                powerInteractor =
-                    PowerInteractor(
-                        repository = FakePowerRepository(),
-                    )
-            )
-            .create(UserSwitcherViewModel::class.java)
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml
deleted file mode 100644
index 5eeb3ad..0000000
--- a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.compose.gallery.tests" >
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.systemui.compose.gallery.tests"
-                     android:label="Tests for SystemUIComposeGallery"/>
-
-</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt
deleted file mode 100644
index 66ecc8d..0000000
--- a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt
+++ /dev/null
@@ -1,36 +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.compose.gallery
-
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.systemui.compose.theme.SystemUITheme
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ScreenshotsTests {
-    @get:Rule val composeRule = createComposeRule()
-
-    @Test
-    fun exampleFeatureScreenshotTest() {
-        // TODO(b/230832101): Wire this with the screenshot diff testing infra. We should reuse the
-        // configuration of the features in the gallery app to populate the UIs.
-        composeRule.setContent { SystemUITheme { ExampleFeatureScreen() } }
-    }
-}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 7839ec8..9ee8c0c 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -493,7 +493,7 @@
 -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/panelstate/PanelExpansionStateManager.kt
--packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/ShadeStateListener.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -812,8 +812,8 @@
 -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/phone/panelstate/PanelExpansionStateManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/ShadeExpansionStateManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -828,7 +828,7 @@
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
index 1237259..506ccf3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
@@ -61,5 +61,13 @@
 
         /** Indicates that the gesture was cancelled and the system should not go back. */
         void cancelBack();
+
+        /**
+         * Indicates if back will be triggered if committed in current state.
+         *
+         * @param triggerBack if back will be triggered in current state.
+         */
+        // TODO(b/247883311): Remove default impl once SwipeBackGestureHandler overrides this.
+        default void setTriggerBack(boolean triggerBack) {}
     }
 }
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 6986961..9d063e9 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -21,11 +21,12 @@
             android:paddingEnd="0dp">
     <item>
         <shape android:shape="rectangle">
-          <solid android:color="?androidprv:attr/colorSurface" />
+          <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
             <corners android:radius="32dp" />
         </shape>
     </item>
     <item
+        android:id="@+id/user_switcher_key_down"
         android:drawable="@drawable/ic_ksh_key_down"
         android:gravity="end|center_vertical"
         android:width="32dp"
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
new file mode 100644
index 0000000..29832a0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/mobile_group"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:orientation="horizontal" >
+
+        <FrameLayout
+            android:id="@+id/inout_container"
+            android:layout_height="17dp"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical">
+            <ImageView
+                android:id="@+id/mobile_in"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_down"
+                android:visibility="gone"
+                android:paddingEnd="2dp"
+                />
+            <ImageView
+                android:id="@+id/mobile_out"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_up"
+                android:paddingEnd="2dp"
+                android:visibility="gone"
+                />
+        </FrameLayout>
+        <ImageView
+            android:id="@+id/mobile_type"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:paddingStart="2.5dp"
+            android:paddingEnd="1dp"
+            android:visibility="gone" />
+        <Space
+            android:id="@+id/mobile_roaming_space"
+            android:layout_height="match_parent"
+            android:layout_width="@dimen/roaming_icon_start_padding"
+            android:visibility="gone"
+            />
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            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"
+                systemui:hasOverlappingRendering="false"
+                />
+            <ImageView
+                android:id="@+id/mobile_roaming"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/stat_sys_roaming"
+                android:contentDescription="@string/data_connection_roaming"
+                android:visibility="gone" />
+        </FrameLayout>
+        <ImageView
+            android:id="@+id/mobile_roaming_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/stat_sys_roaming_large"
+            android:contentDescription="@string/data_connection_roaming"
+            android:visibility="gone" />
+    </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 0000000..1b38fd2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mobile_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical" >
+
+    <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index ae8680a0..5ee67d91 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -37,7 +37,7 @@
     <string name="keyguard_missing_sim_instructions" msgid="1162120926141335918">"Introdu un card SIM."</string>
     <string name="keyguard_missing_sim_instructions_long" msgid="2712623293749378570">"Cardul SIM lipsește sau nu poate fi citit. Introdu un card SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="5842745213110966962">"Card SIM inutilizabil."</string>
-    <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"Cardul dvs. SIM este dezactivat definitiv.\n Contactați furnizorul de servicii wireless pentru a obține un alt card SIM."</string>
+    <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"Cardul SIM e dezactivat definitiv.\n Contactează furnizorul de servicii wireless pentru a obține un alt card SIM."</string>
     <string name="keyguard_sim_locked_message" msgid="4343544458476911044">"Cardul SIM este blocat."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="6253830777745450550">"Cardul SIM este blocat cu codul PUK."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"Se deblochează cardul SIM…"</string>
@@ -46,7 +46,7 @@
     <string name="keyguard_accessibility_sim_pin_area" msgid="6272116591533888062">"Zona codului PIN pentru cardul SIM"</string>
     <string name="keyguard_accessibility_sim_puk_area" msgid="5537294043180237374">"Zona codului PUK pentru cardul SIM"</string>
     <string name="keyboardview_keycode_delete" msgid="8489719929424895174">"Șterge"</string>
-    <string name="disable_carrier_button_text" msgid="7153361131709275746">"Dezactivați cardul eSIM"</string>
+    <string name="disable_carrier_button_text" msgid="7153361131709275746">"Dezactivează cardul eSIM"</string>
     <string name="error_disable_esim_title" msgid="3802652622784813119">"Nu se poate dezactiva cardul eSIM"</string>
     <string name="error_disable_esim_msg" msgid="2441188596467999327">"Cardul eSIM nu poate fi dezactivat din cauza unei erori."</string>
     <string name="keyboardview_keycode_enter" msgid="6727192265631761174">"Introdu"</string>
@@ -56,24 +56,24 @@
     <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Reîncearcă peste o secundă.}few{Reîncearcă peste # secunde.}other{Reîncearcă peste # de secunde.}}"</string>
     <string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Introdu codul PIN al cardului SIM."</string>
     <string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Introdu codul PIN al cardului SIM pentru „<xliff:g id="CARRIER">%1$s</xliff:g>”."</string>
-    <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Dezactivați cardul eSIM pentru a folosi dispozitivul fără serviciu mobil."</string>
-    <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"Cardul SIM este acum dezactivat. Pentru a continua, introduceți codul PUK. Pentru detalii, contactați operatorul."</string>
-    <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"Cardul SIM „<xliff:g id="CARRIER">%1$s</xliff:g>\" este acum dezactivat. Pentru a continua, introduceți codul PUK. Pentru detalii, contactați operatorul."</string>
+    <string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Dezactivează cardul eSIM pentru a folosi dispozitivul fără serviciu mobil."</string>
+    <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"Cardul SIM e acum dezactivat. Pentru a continua, introdu codul PUK. Pentru detalii, contactează operatorul."</string>
+    <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"Cardul SIM „<xliff:g id="CARRIER">%1$s</xliff:g>\" e acum dezactivat. Pentru a continua, introdu codul PUK. Pentru detalii, contactează operatorul."</string>
     <string name="kg_puk_enter_pin_hint" msgid="6028432138916150399">"Introdu codul PIN dorit"</string>
-    <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Confirmați codul PIN dorit"</string>
+    <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Confirmă codul PIN dorit"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"Se deblochează cardul SIM…"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"Introdu un cod PIN alcătuit din 4 până la 8 cifre."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"Codul PUK trebuie să aibă minimum 8 cifre."</string>
-    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"Ați introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
-    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"Ați introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
-    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncercați din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
-    <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Codul PIN pentru cardul SIM este incorect. Contactați operatorul pentru a vă debloca dispozitivul."</string>
-    <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Codul PIN pentru cardul SIM este incorect. V-a mai rămas # încercare, după care va trebui să contactați operatorul pentru a vă debloca dispozitivul.}few{Codul PIN pentru cardul SIM este incorect. V-au mai rămas # încercări. }other{Codul PIN pentru cardul SIM este incorect. V-au mai rămas # de încercări. }}"</string>
-    <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"Cardul SIM nu poate fi utilizat. Contactați operatorul."</string>
-    <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Codul PUK pentru cardul SIM este incorect. V-a mai rămas # încercare până când cardul SIM va deveni inutilizabil definitiv.}few{Codul PUK pentru cardul SIM este incorect. V-au mai rămas # încercări până când cardul SIM va deveni inutilizabil definitiv.}other{Codul PUK pentru cardul SIM este incorect. V-au mai rămas # de încercări până când cardul SIM va deveni inutilizabil definitiv.}}"</string>
+    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"Ai introdus incorect codul PIN de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori.\n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"Ai introdus incorect parola de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. \n\nÎncearcă din nou peste <xliff:g id="NUMBER_1">%2$d</xliff:g> secunde."</string>
+    <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Codul PIN pentru cardul SIM este incorect. Contactează operatorul pentru a debloca dispozitivul."</string>
+    <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{PIN-ul cardului SIM e incorect. Ți-a mai rămas # încercare, după care va trebui să contactezi operatorul pentru a debloca dispozitivul.}few{PIN-ul cardului SIM e incorect. Ți-au mai rămas # încercări. }other{PIN-ul cardului SIM e incorect. Ți-au mai rămas # de încercări. }}"</string>
+    <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"Cardul SIM nu poate fi utilizat. Contactează operatorul."</string>
+    <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Codul PUK pentru cardul SIM e incorect. Ți-a mai rămas # încercare până când cardul SIM va deveni inutilizabil definitiv.}few{Codul PUK pentru cardul SIM e incorect. Ți-au mai rămas # încercări până când cardul SIM va deveni inutilizabil definitiv.}other{Codul PUK pentru cardul SIM e incorect. Ți-au mai rămas # de încercări până când cardul SIM va deveni inutilizabil definitiv.}}"</string>
     <string name="kg_password_pin_failed" msgid="5136259126330604009">"Deblocarea cu ajutorul codului PIN pentru cardul SIM nu a reușit!"</string>
     <string name="kg_password_puk_failed" msgid="6778867411556937118">"Deblocarea cu ajutorul codului PUK pentru cardul SIM nu a reușit!"</string>
-    <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Comutați metoda de introducere"</string>
+    <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Schimbă metoda de introducere"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mod Avion"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
@@ -84,9 +84,9 @@
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
-    <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Pentru Deblocare facială, activați accesul la cameră"</string>
-    <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Introduceți codul PIN pentru cardul SIM. V-a mai rămas # încercare, după care va trebui să contactați operatorul pentru a vă debloca dispozitivul.}few{Introduceți codul PIN al cardului SIM. V-au rămas # încercări.}other{Introduceți codul PIN al cardului SIM. V-au rămas # de încercări.}}"</string>
-    <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{Cardul SIM este dezactivat acum. Introduceți codul PUK pentru a continua. V-a mai rămas # încercare până când cardul SIM va deveni inutilizabil definitiv. Contactați operatorul pentru detalii.}few{Cardul SIM este dezactivat acum. Introduceți codul PUK pentru a continua. V-au mai rămas # încercări până când cardul SIM va deveni inutilizabil definitiv. Contactați operatorul pentru detalii.}other{Cardul SIM este dezactivat acum. Introduceți codul PUK pentru a continua. V-au mai rămas # de încercări până când cardul SIM va deveni inutilizabil definitiv. Contactați operatorul pentru detalii.}}"</string>
+    <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Pentru Deblocare facială, activează accesul la cameră"</string>
+    <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Introdu PIN-ul cardului SIM. Ți-a mai rămas # încercare, după care va trebui să contactezi operatorul pentru a debloca dispozitivul.}few{Introdu PIN-ul cardului SIM. Ți-au rămas # încercări.}other{Introdu PIN-ul cardului SIM. Ți-au rămas # de încercări.}}"</string>
+    <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{Cardul SIM e acum dezactivat. Introdu codul PUK pentru a continua. Ți-a mai rămas # încercare până când cardul SIM va deveni inutilizabil definitiv. Contactează operatorul pentru detalii.}few{Cardul SIM e acum dezactivat. Introdu codul PUK pentru a continua. Ți-au mai rămas # încercări până când cardul SIM va deveni inutilizabil definitiv. Contactează operatorul pentru detalii.}other{Cardul SIM e acum dezactivat. Introdu codul PUK pentru a continua. Ți-au mai rămas # de încercări până când cardul SIM va deveni inutilizabil definitiv. Contactează operatorul pentru detalii.}}"</string>
     <string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
diff --git a/packages/SystemUI/res-keyguard/values/ids.xml b/packages/SystemUI/res-keyguard/values/ids.xml
new file mode 100644
index 0000000..0dff4ff
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/ids.xml
@@ -0,0 +1,20 @@
+<?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>
+    <item type="id" name="header_footer_views_added_tag_key" />
+</resources>
diff --git a/packages/SystemUI/res-product/values-ro/strings.xml b/packages/SystemUI/res-product/values-ro/strings.xml
index 54dc73a..471f01e 100644
--- a/packages/SystemUI/res-product/values-ro/strings.xml
+++ b/packages/SystemUI/res-product/values-ro/strings.xml
@@ -19,30 +19,30 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"Repoziționați telefonul pentru încărcare mai rapidă"</string>
-    <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"Repoziționați telefonul pentru încărcarea wireless"</string>
-    <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Dispozitivul Android TV se va opri în curând. Apăsați un buton pentru a-l menține pornit."</string>
-    <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Dispozitivul se va opri în curând. Apăsați pentru a-l menține pornit."</string>
+    <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"Repoziționează telefonul pentru încărcare mai rapidă"</string>
+    <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"Repoziționează telefonul pentru încărcarea wireless"</string>
+    <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Dispozitivul Android TV se va opri în curând. Apasă un buton pentru a-l menține pornit."</string>
+    <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"Dispozitivul se va opri în curând. Apasă pentru a-l menține pornit."</string>
     <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Nu există card SIM în tabletă."</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Nu există card SIM în telefon."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"Codurile PIN nu coincid"</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, această tabletă va fi resetată, iar toate datele acesteia vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Această tabletă va fi resetată, iar toate datele acesteia vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Ați efectuat <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
-    <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Ați efectuat <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați tableta cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
-    <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Deblocați telefonul pentru mai multe opțiuni"</string>
-    <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Deblocați tableta pentru mai multe opțiuni"</string>
-    <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Deblocați dispozitivul pentru mai multe opțiuni"</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, tableta va fi resetată, iar toate datele vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Această tabletă va fi resetată, iar toate datele vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest telefon va fi resetat, iar toate datele acestuia vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Acest utilizator va fi eliminat, iar toate datele utilizatorului vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a tabletei. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Ai făcut <xliff:g id="NUMBER_0">%1$d</xliff:g> încercări incorecte de deblocare a telefonului. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a tabletei. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
+    <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Ai făcut <xliff:g id="NUMBER">%d</xliff:g> încercări incorecte de deblocare a telefonului. Profilul de serviciu va fi eliminat, iar toate datele profilului vor fi șterse."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi tableta cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Ai desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, ți se va solicita să deblochezi telefonul cu ajutorul unui cont de e-mail.\n\n Încearcă din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string>
+    <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Deblochează telefonul pentru mai multe opțiuni"</string>
+    <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Deblochează tableta pentru mai multe opțiuni"</string>
+    <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Deblochează dispozitivul pentru mai multe opțiuni"</string>
     <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Se redă pe acest telefon"</string>
     <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Se redă pe această tabletă"</string>
 </resources>
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
index 420ae70..e015ed3 100644
--- a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
@@ -32,23 +32,6 @@
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_0_G_D_0_P_1">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="fillColor"
-                    android:startOffset="0"
-                    android:valueFrom="#000000"
-                    android:valueTo="#ffffff"
-                    android:valueType="colorType">
-                    <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">
@@ -72,29 +55,13 @@
                 <group
                     android:name="_R_G_L_0_G"
                     android:translateX="12"
-                    android:translateY="12.469">
-                    <group
-                        android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
-                        android:scaleX="1"
-                        android:scaleY="1">
-                        <path
-                            android:name="_R_G_L_0_G_D_0_P_0"
-                            android:fillAlpha="1"
-                            android:fillColor="#000000"
-                            android:fillType="nonZero"
-                            android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " />
-                    </group>
-                    <group
-                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
-                        android:scaleX="1"
-                        android:scaleY="1">
-                        <path
-                            android:name="_R_G_L_0_G_D_0_P_1"
-                            android:fillAlpha="1"
-                            android:fillColor="#000000"
-                            android:fillType="nonZero"
-                            android:pathData=" M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " />
-                    </group>
+                    android:translateY="11.969">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#000000"
+                        android:fillType="nonZero"
+                        android:pathData=" M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " />
                 </group>
             </group>
             <group android:name="time_group" />
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
index 57d7902..a44a9b6 100644
--- a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
@@ -58,25 +58,47 @@
                     </aapt:attr>
                 </objectAnimator>
                 <objectAnimator
-                    android:duration="767"
+                    android:duration="67"
                     android:propertyName="scaleX"
                     android:startOffset="383"
                     android:valueFrom="1.1500000000000001"
-                    android:valueTo="1"
+                    android:valueTo="1.1500000000000001"
                     android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
                 <objectAnimator
-                    android:duration="767"
+                    android:duration="67"
                     android:propertyName="scaleY"
                     android:startOffset="383"
                     android:valueFrom="1.1500000000000001"
+                    android:valueTo="1.1500000000000001"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="700"
+                    android:propertyName="scaleX"
+                    android:startOffset="450"
+                    android:valueFrom="1.1500000000000001"
                     android:valueTo="1"
                     android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="700"
+                    android:propertyName="scaleY"
+                    android:startOffset="450"
+                    android:valueFrom="1.1500000000000001"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.4,1 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -89,141 +111,24 @@
                     android:duration="200"
                     android:propertyName="pathData"
                     android:startOffset="0"
-                    android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
-                    android:valueTo="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c "
+                    android:valueFrom="M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c "
+                    android:valueTo="M-3.23 8.38 C-3.23,8.38 -3.23,9.4 -3.23,9.4 C-3.23,9.4 0,8.51 0,8.51 C0,8.51 3.23,9.4 3.23,9.4 C3.23,9.4 3.23,8.38 3.23,8.38 C3.23,8.38 1.26,6.51 1.26,6.51 C1.26,6.51 1.59,1.5 1.59,1.5 C1.59,1.5 10,3.66 10,3.66 C10,3.66 10,1.95 10,1.95 C10,1.95 1.59,-3.36 1.59,-3.36 C1.59,-3.36 1.77,-8.02 1.77,-8.02 C1.77,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.77,-8.44 -1.77,-8.02 C-1.77,-8.02 -1.59,-3.36 -1.59,-3.36 C-1.59,-3.36 -10,1.95 -10,1.95 C-10,1.95 -10,3.66 -10,3.66 C-10,3.66 -1.59,1.5 -1.59,1.5 C-1.59,1.5 -1.24,6.51 -1.24,6.51 C-1.24,6.51 -3.23,8.38 -3.23,8.38c "
                     android:valueType="pathType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="217"
-                    android:propertyName="pathData"
-                    android:startOffset="200"
-                    android:valueFrom="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c "
-                    android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
-                    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:duration="217"
-                    android:propertyName="pathData"
-                    android:startOffset="417"
-                    android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
-                    android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c "
-                    android:valueType="pathType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_1">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="fillColor"
-                    android:startOffset="0"
-                    android:valueFrom="#ffffff"
-                    android:valueTo="#000000"
-                    android:valueType="colorType">
-                    <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_1_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="383"
-                    android:propertyName="scaleX"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1.1500000000000001"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
                 <objectAnimator
                     android:duration="383"
-                    android:propertyName="scaleY"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1.1500000000000001"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="767"
-                    android:propertyName="scaleX"
-                    android:startOffset="383"
-                    android:valueFrom="1.1500000000000001"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="767"
-                    android:propertyName="scaleY"
-                    android:startOffset="383"
-                    android:valueFrom="1.1500000000000001"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_1">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="200"
                     android:propertyName="pathData"
-                    android:startOffset="0"
-                    android:valueFrom="M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c "
-                    android:valueTo="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c "
+                    android:startOffset="200"
+                    android:valueFrom="M-3.23 8.38 C-3.23,8.38 -3.23,9.4 -3.23,9.4 C-3.23,9.4 0,8.51 0,8.51 C0,8.51 3.23,9.4 3.23,9.4 C3.23,9.4 3.23,8.38 3.23,8.38 C3.23,8.38 1.26,6.51 1.26,6.51 C1.26,6.51 1.59,1.5 1.59,1.5 C1.59,1.5 10,3.66 10,3.66 C10,3.66 10,1.95 10,1.95 C10,1.95 1.59,-3.36 1.59,-3.36 C1.59,-3.36 1.77,-8.02 1.77,-8.02 C1.77,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.77,-8.44 -1.77,-8.02 C-1.77,-8.02 -1.59,-3.36 -1.59,-3.36 C-1.59,-3.36 -10,1.95 -10,1.95 C-10,1.95 -10,3.66 -10,3.66 C-10,3.66 -1.59,1.5 -1.59,1.5 C-1.59,1.5 -1.24,6.51 -1.24,6.51 C-1.24,6.51 -3.23,8.38 -3.23,8.38c "
+                    android:valueTo="M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c "
                     android:valueType="pathType">
                     <aapt:attr name="android:interpolator">
                         <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="217"
-                    android:propertyName="pathData"
-                    android:startOffset="200"
-                    android:valueFrom="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c "
-                    android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
-                    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:duration="217"
-                    android:propertyName="pathData"
-                    android:startOffset="417"
-                    android:valueFrom="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
-                    android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c "
-                    android:valueType="pathType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
             </set>
         </aapt:attr>
     </target>
@@ -250,7 +155,7 @@
                 <group
                     android:name="_R_G_L_0_G"
                     android:translateX="12"
-                    android:translateY="12.469">
+                    android:translateY="11.969">
                     <group
                         android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
                         android:scaleX="1"
@@ -260,18 +165,7 @@
                             android:fillAlpha="1"
                             android:fillColor="#ffffff"
                             android:fillType="nonZero"
-                            android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " />
-                    </group>
-                    <group
-                        android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"
-                        android:scaleX="1"
-                        android:scaleY="1">
-                        <path
-                            android:name="_R_G_L_0_G_D_0_P_1"
-                            android:fillAlpha="1"
-                            android:fillColor="#ffffff"
-                            android:fillType="nonZero"
-                            android:pathData=" M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " />
+                            android:pathData=" M-3.5 8.5 C-3.5,8.5 -3.5,10 -3.5,10 C-3.5,10 0,9 0,9 C0,9 3.5,10 3.5,10 C3.5,10 3.5,8.5 3.5,8.5 C3.5,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.02 1.5,-8.02 C1.5,-8.44 1.2,-9.52 0,-9.52 C-1.19,-9.52 -1.5,-8.44 -1.5,-8.02 C-1.5,-8.02 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -3.5,8.5 -3.5,8.5c " />
                     </group>
                 </group>
             </group>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
new file mode 100644
index 0000000..eb160de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
@@ -0,0 +1,99 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="133"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    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_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="133"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    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="150"
+                    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="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
new file mode 100644
index 0000000..de972a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
@@ -0,0 +1,110 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    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_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="0.3"
+                    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="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="167"
+                    android:valueFrom="0.3"
+                    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="433"
+                    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="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
new file mode 100644
index 0000000..e33b264
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
@@ -0,0 +1,132 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    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="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="250"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    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_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="0.3"
+                    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="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="167"
+                    android:valueFrom="0.3"
+                    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="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="417"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    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="850"
+                    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="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index acb47f7..2d67d95 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,8 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<com.android.systemui.dreams.complication.DoubleShadowTextClock
+<com.android.systemui.shared.shadow.DoubleShadowTextClock
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/time_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -25,4 +26,13 @@
     android:format24Hour="@string/dream_time_complication_24_hr_time_format"
     android:fontFeatureSettings="pnum, lnum"
     android:letterSpacing="0.02"
-    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
+    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+    app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+    app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+    app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+    app:keyShadowAlpha="0.3"
+    app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+    app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+    app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+    app:ambientShadowAlpha="0.3"
+/>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index 4ad6849..226bc6a 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -36,13 +36,14 @@
         android:background="@*android:drawable/bottomsheet_background">
 
         <ImageView
-            android:id="@*android:id/icon"
             android:layout_width="@dimen/media_projection_app_selector_icon_size"
             android:layout_height="@dimen/media_projection_app_selector_icon_size"
             android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal"
             android:layout_marginBottom="@*android:dimen/chooser_edge_margin_normal"
             android:importantForAccessibility="no"
-            android:tint="?android:attr/textColorPrimary"/>
+            android:tint="?android:attr/textColorPrimary"
+            android:src="@drawable/ic_present_to_all"
+            />
 
         <TextView android:id="@*android:id/title"
             android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
new file mode 100644
index 0000000..a2b3c40
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
@@ -0,0 +1,54 @@
+<?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:gravity="center"
+    android:orientation="vertical"
+    android:background="?android:attr/colorBackground"
+    >
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="256dp">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/media_projection_recent_tasks_recycler"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            />
+
+        <ProgressBar
+            android:id="@+id/media_projection_recent_tasks_loader"
+            android:layout_width="@dimen/media_projection_app_selector_loader_size"
+            android:layout_height="@dimen/media_projection_app_selector_loader_size"
+            android:layout_gravity="center"
+            android:indeterminate="true"
+            android:indeterminateOnly="true" />
+    </FrameLayout>
+
+    <!-- Divider line -->
+    <ImageView
+        android:layout_width="72dp"
+        android:layout_height="2dp"
+        android:layout_marginBottom="8dp"
+        android:layout_marginTop="24dp"
+        android:importantForAccessibility="no"
+        android:src="@*android:drawable/ic_drag_handle"
+        android:tint="?android:attr/textColorSecondary" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml
new file mode 100644
index 0000000..75f73cd
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_projection_task_item.xml
@@ -0,0 +1,38 @@
+<?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="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/task_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_margin="8dp"
+        android:importantForAccessibility="no" />
+
+    <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly -->
+    <!-- TODO(b/240924926) dynamically change the view size based on the screen size -->
+    <ImageView
+        android:id="@+id/task_thumbnail"
+        android:layout_width="100dp"
+        android:layout_height="216dp"
+        android:scaleType="centerCrop"
+        />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index d886806..ae8e38e 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -16,7 +16,7 @@
 <!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
      is the root view of a window, we cannot change the root view's margins.) -->
 <!-- Alphas start as 0 because the view will be animated in. -->
-<FrameLayout
+<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
             />
 
     </LinearLayout>
-</FrameLayout>
+</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b3..d6c63eb 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
 -->
 <com.android.systemui.statusbar.StatusBarMobileView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/mobile_combo"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:gravity="center_vertical" >
 
-    <com.android.keyguard.AlphaOptimizedLinearLayout
-        android:id="@+id/mobile_group"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center_vertical"
-        android:orientation="horizontal" >
+    <include layout="@layout/status_bar_mobile_signal_group_inner" />
 
-        <FrameLayout
-            android:id="@+id/inout_container"
-            android:layout_height="17dp"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical">
-            <ImageView
-                android:id="@+id/mobile_in"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_down"
-                android:visibility="gone"
-                android:paddingEnd="2dp"
-            />
-            <ImageView
-                android:id="@+id/mobile_out"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2dp"
-                android:visibility="gone"
-            />
-        </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_type"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:paddingStart="2.5dp"
-            android:paddingEnd="1dp"
-            android:visibility="gone" />
-        <Space
-            android:id="@+id/mobile_roaming_space"
-            android:layout_height="match_parent"
-            android:layout_width="@dimen/roaming_icon_start_padding"
-            android:visibility="gone"
-        />
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            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"
-                systemui:hasOverlappingRendering="false"
-            />
-            <ImageView
-                android:id="@+id/mobile_roaming"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:src="@drawable/stat_sys_roaming"
-                android:contentDescription="@string/data_connection_roaming"
-                android:visibility="gone" />
-        </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_roaming_large"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/stat_sys_roaming_large"
-            android:contentDescription="@string/data_connection_roaming"
-            android:visibility="gone" />
-    </com.android.keyguard.AlphaOptimizedLinearLayout>
 </com.android.systemui.statusbar.StatusBarMobileView>
 
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 0c57b934..8388b67 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -103,6 +103,7 @@
                      android:layout_width="match_parent"
                      android:layout_weight="1"
                      android:background="@android:color/transparent"
+                     android:visibility="invisible"
                      android:clipChildren="false"
                      android:clipToPadding="false" />
     </LinearLayout>
diff --git a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json
index b161609..09ed225 100644
--- a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json
+++ b/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json
@@ -1 +1 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_topleft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey905","cl":"grey905","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey904","cl":"grey904","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey903","cl":"grey903","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey902","cl":"grey902","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey901","cl":"grey901","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 7f6f006..c773177 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Sommige kenmerke is beperk terwyl foon afkoel.\nTik vir meer inligting"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Jou foon sal outomaties probeer om af te koel. Jy kan steeds jou foon gebruik, maar dit sal dalk stadiger wees.\n\nJou foon sal normaalweg werk nadat dit afgekoel het."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sien versorgingstappe"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Prop jou toestel uit"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Jou toestel word tans warm naby die laaipoort. Prop dit uit as dit aan ’n laaier of USB-bykomstigheid gekoppel is. Wees versigtig, aangesien die kabel dalk ook warm is."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Sien versorgingstappe"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Links-kortpad"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Regs-kortpad"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index a3ebb60..3df515b 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"በርቷል"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ጠፍቷል"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"አይገኝም"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"የበለጠ ለመረዳት"</string>
     <string name="nav_bar" msgid="4642708685386136807">"የአሰሳ አሞሌ"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"አቀማመጥ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ተጨማሪ የግራ አዝራር ዓይነት"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"አንዳንድ ባሕሪያት ስልኩ እየቀዘቀዘ እያለ ውስን ይሆናሉ።\nለተጨማሪ መረጃ መታ ያድርጉ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"የእርስዎ ስልክ በራስ-ሰር ለመቀዝቀዝ ይሞክራል። አሁንም ስልክዎን መጠቀም ይችላሉ፣ ነገር ግን ሊንቀራፈፍ ይችላል።\n\nአንዴ ስልክዎ ከቀዘቀዘ በኋላ በመደበኝነት ያሄዳል።"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"መሣሪያዎን ይንቀሉ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"መሣሪያዎ ከኃይል መሙያ ወደቡ አቅራቢያ እየሞቀ ነው። ከኃይል መሙያ ወይም ከዩኤስቢ ተጨማሪ መሣሪያ ጋር ከተገናኘ ይንቀሉት እና ገመዱ የሞቀ ሊሆን ስለሚችል ጥንቃቄ ያድርጉ።"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"የግራ አቋራጭ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"የቀኝ አቋራጭ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 5a35e90..7672fc2 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"مفعّل"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"متوقف"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"غير متوفّر"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"مزيد من المعلومات"</string>
     <string name="nav_bar" msgid="4642708685386136807">"شريط التنقل"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"التنسيق"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"نوع زر اليسار الإضافي"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"يتم تقييد عمل بعض الميزات إلى أن تنخفض درجة حرارة الهاتف.\nانقر للحصول على مزيد من المعلومات."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"سيحاول الهاتف تخفيض درجة حرارته تلقائيًا. سيظل بإمكانك استخدام هاتفك، ولكن قد يعمل بشكل أبطأ.\n\nبعد أن تنخفض درجة حرارة الهاتف، سيستعيد سرعته المعتادة."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"الاطّلاع على خطوات العناية"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"افصِل جهازك"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"‏تزداد حرارة الجهاز بالقرب من منفذ الشحن. إذا كان الجهاز متصلاً بشاحن أو ملحق USB، عليك فصله وتوخي الحذر لأن درجة حرارة الكابل قد تكون مرتفعة أيضًا."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"الاطّلاع على خطوات العناية"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"اختصار اليسار"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"اختصار اليمين"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 912c82e..990c812 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"অন"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"অফ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"উপলব্ধ নহয়"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"অধিক জানক"</string>
     <string name="nav_bar" msgid="4642708685386136807">"নেভিগেশ্বন দণ্ড"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"লেআউট"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"বাওঁ বুটামৰ অতিৰিক্ত প্ৰকাৰ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"বাওঁ শ্বৰ্টকাট"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"সোঁ শ্বৰ্টকাট"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index fd2d848..b21061a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Aktiv"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Deaktiv"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Əlçatan deyil"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ətraflı məlumat"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Naviqasiya paneli"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Tərtibat"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Əlavə sol düymə növü"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Telefon soyuyana kimi bəzi funksiyalar məhdudlaşdırılır.\nƏtraflı məlumat üçün toxunun"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonunuz avtomatik olaraq soyumağa başlayacaq. Telefon istifadəsinə davam edə bilərsiniz, lakin sürəti yavaşlaya bilər.\n\nTelefonunuz soyuduqdan sonra normal işləyəcək."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızı ayırın"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Cihazınız şarj portunun yaxınlığında qızmağa başlayır. Şarj cihazına və ya USB aksesuarına qoşulubsa, onu ayırın və diqqətli olun, çünki kabel də qıza bilər."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Sol qısayol"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Sağ qısayol"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index dbef1e4..78582cd 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Traka za navigaciju"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Raspored"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Dodatni tip levog dugmeta"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke funkcije su ograničene dok se telefon ne ohladi.\nDodirnite za više informacija"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon će automatski pokušati da se ohladi. I dalje ćete moći da koristite telefon, ali će sporije reagovati.\n\nKada se telefon ohladi, normalno će raditi."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte upozorenja"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Isključite uređaj"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Uređaj se zagreva u blizini porta za punjenje. Ako je povezan sa punjačem ili USB opremom, isključite je i budite pažljivi jer i kabl može da bude vruć."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Pogledajte upozorenja"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Leva prečica"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desna prečica"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 372392c..f1a84bc 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Уключана"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Выключана"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Недаступна"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"даведацца больш"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Панэль навігацыі"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Раскладка"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Дадатковы тып кнопкі \"ўлева\""</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Некаторыя функцыі абмежаваны, пакуль тэлефон не астыне.\nНацісніце, каб даведацца больш"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш тэлефон аўтаматычна паспрабуе астыць. Вы можаце па-ранейшаму карыстацца сваім тэлефонам, але ён можа працаваць больш павольна.\n\nПасля таго як ваш тэлефон астыне, ён будзе працаваць у звычайным рэжыме."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Глядзець паэтапную дапамогу"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Адключыце прыладу"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Ваша прылада моцна награваецца ў месцы, дзе знаходзіцца зарадны порт. Калі яна падключана да зараднай прылады ці USB-прылады, адключыце яе і будзьце асцярожнымі з кабелем, які таксама можа награвацца."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Глядзець паэтапную дапамогу"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ярлык \"улева\""</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Ярлык \"управа\""</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1bca6a4..46bd5c2 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Вкл."</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Изкл."</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Не е налице"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"научете повече"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Лента за навигация"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Оформление"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Тип на допълнителния ляв бутон"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Някои функции са ограничени, докато телефонът се охлажда.\nДокоснете за още информация"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонът ви автоматично ще направи опит за охлаждане. Пак можете да го използвате, но той може да работи по-бавно.\n\nСлед като се охлади, ще работи нормално."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Вижте стъпките, които да предприемете"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Изключете устройството си"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Устройството ви се загрява до порта за зареждане. Ако е свързано със зарядно устройство или аксесоар за USB, изключете го и внимавайте, тъй като и кабелът може да е топъл."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Вижте стъпките, които да предприемете"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ляв пряк път"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десен пряк път"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 80beebc..15cd726 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ফোন ঠাণ্ডা না হওয়া পর্যন্ত কিছু ফিচার কাজ করে না।\nআরও তথ্যের জন্য ট্যাপ করুন"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপনার ফোনটি নিজে থেকেই ঠাণ্ডা হওয়ার চেষ্টা করবে৷ আপনি তবুও আপনার ফোন ব্যবহার করতে পারেন, কিন্তু এটি একটু ধীরে চলতে পারে৷\n\nআপনার ফোনটি পুরোপুরি ঠাণ্ডা হয়ে গেলে এটি স্বাভাবিকভাবে চলবে৷"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"আপনার ডিভাইস আনপ্লাগ করা"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"চার্জিং পোর্টের কাছে আপনার ডিভাইসটি গরম হচ্ছে। এটি চার্জার বা ইউএসবি অ্যাক্সেসরির সাথে কানেক্ট করা থাকলে, আনপ্লাগ করুন এবং সতর্ক থাকুন কারণ কেবেলটিও গরম হতে পারে।"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"কী করতে হবে ধাপে ধাপে দেখুন"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"বাঁদিকের শর্টকাট"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ডানদিকের শর্টকাট"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ecb0e24..8214ce0 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigaciona traka"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Raspored"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnog dugmeta lijevo"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke funkcije su ograničene dok se telefon hladi.\nDodirnite za više informacija"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Vaš telefon će se automatski pokušati ohladiti. I dalje možete koristi telefon, ali će možda raditi sporije.\n\nNakon što se ohladi, telefon će normalno raditi."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte korake za zaštitu"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Uređaj se zagrijava u blizini priključka za punjenje. Ako je povezan s punjačem ili USB dodatkom, iskopčajte ga i vodite računa jer i kabl može biti topao."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Prikaz koraka za zaštitu"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Prečica lijevo"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Prečica desno"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index bbffb02..7657933 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -251,7 +251,7 @@
     <string name="quick_settings_connecting" msgid="2381969772953268809">"S\'està connectant..."</string>
     <string name="quick_settings_hotspot_label" msgid="1199196300038363424">"Punt d\'accés Wi-Fi"</string>
     <string name="quick_settings_hotspot_secondary_label_transient" msgid="7585604088079160564">"S\'està activant…"</string>
-    <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Economitzador activat"</string>
+    <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Estalvi dades activat"</string>
     <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# dispositiu}other{# dispositius}}"</string>
     <string name="quick_settings_flashlight_label" msgid="4904634272006284185">"Llanterna"</string>
     <string name="quick_settings_flashlight_camera_in_use" msgid="4820591564526512571">"Càmera en ús"</string>
@@ -593,13 +593,12 @@
     <string name="accessibility_long_click_tile" msgid="210472753156768705">"Obre la configuració"</string>
     <string name="accessibility_status_bar_headphones" msgid="1304082414912647414">"Auriculars connectats"</string>
     <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Auriculars connectats"</string>
-    <string name="data_saver" msgid="3484013368530820763">"Economitzador de dades"</string>
-    <string name="accessibility_data_saver_on" msgid="5394743820189757731">"L\'Economitzador de dades està activat"</string>
+    <string name="data_saver" msgid="3484013368530820763">"Estalvi de dades"</string>
+    <string name="accessibility_data_saver_on" msgid="5394743820189757731">"L\'Estalvi de dades està activat"</string>
     <string name="switch_bar_on" msgid="1770868129120096114">"Activat"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desactivat"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"més informació"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegació"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Disposició"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipus de botó addicional de l\'esquerra"</string>
@@ -624,7 +623,7 @@
     <string name="right_keycode" msgid="2480715509844798438">"Codi de tecla de la dreta"</string>
     <string name="left_icon" msgid="5036278531966897006">"Icona de l\'esquerra"</string>
     <string name="right_icon" msgid="1103955040645237425">"Icona de la dreta"</string>
-    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén premut i arrossega per afegir mosaics"</string>
+    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén premut i arrossega per afegir icones"</string>
     <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén premut i arrossega per reorganitzar els mosaics"</string>
     <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrossega aquí per suprimir"</string>
     <string name="drag_to_remove_disabled" msgid="933046987838658850">"Necessites com a mínim <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> mosaics"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Algunes funcions estan limitades mentre el telèfon es refreda.\nToca per obtenir més informació"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"El telèfon provarà de refredar-se automàticament. Podràs continuar utilitzant-lo, però és possible que funcioni més lentament.\n\nUn cop s\'hagi refredat, funcionarà amb normalitat."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Mostra els passos de manteniment"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconnecta el dispositiu"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"El dispositiu s\'està escalfant a prop del port de càrrega. Si està connectat a un carregador o a un accessori USB, desconnecta\'l. Ves amb compte perquè el cable també pot haver-se escalfat."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Mostra els pasos de manteniment"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Drecera de l\'esquerra"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Drecera de la dreta"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 66874fd..70737ad 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Zapnuto"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Vypnuto"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupné"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"další informace"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigační panel"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Rozvržení"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Zvláštní typ tlačítka vlevo"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Některé funkce jsou při chladnutí telefonu omezeny.\nKlepnutím zobrazíte další informace"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon se automaticky pokusí vychladnout. Lze jej nadále používat, ale může být pomalejší.\n\nAž telefon vychladne, bude fungovat normálně."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobrazit pokyny, co dělat"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zařízení"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Zařízení se zahřívá v oblasti nabíjecího portu. Pokud je připojeno k nabíječce nebo příslušenství USB, odpojte ho (dejte pozor, kabel také může být zahřátý)."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobrazit pokyny, co dělat"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Zkratka vlevo"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Zkratka vpravo"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ab201d7..65228d5 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Til"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Fra"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Ikke tilgængelig"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"få flere oplysninger"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigationslinje"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra venstre knaptype"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Nogle funktioner er begrænsede, mens telefonen køler ned.\nTryk for at få flere oplysninger"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Din telefon forsøger automatisk at køle ned. Du kan stadig bruge telefonen, men den kører muligvis langsommere.\n\nNår din telefon er kølet ned, fungerer den normalt igen."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se håndteringsvejledning"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Træk stikket ud af din enhed"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Din enhed er ved at blive varm i nærheden af opladningsporten. Hvis enheden er tilsluttet en oplader eller USB-enhed, skal du trække stikket ud. Vær opmærksom på, at stikket også kan være varmt."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Se vejledningen i pleje"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Venstre genvej"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Højre genvej"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 5bf907c..c0ba5dc 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"An"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Aus"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nicht verfügbar"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Weitere Informationen"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigationsleiste"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Zusätzlicher linker Schaltflächentyp"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Einige Funktionen sind während der Abkühlphase des Smartphones eingeschränkt.\nFür mehr Informationen tippen."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Dein Smartphone kühlt sich automatisch ab. Du kannst dein Smartphone weiterhin nutzen, aber es reagiert möglicherweise langsamer.\n\nSobald dein Smartphone abgekühlt ist, funktioniert es wieder normal."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Schritte zur Abkühlung des Geräts ansehen"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Gerät vom Stromnetz trennen"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Dein Gerät erwärmt sich am Ladeanschluss. Trenne das Gerät vom Stromnetz, wenn es an ein Ladegerät oder USB-Zubehör angeschlossen ist. Sei vorsichtig, denn das Kabel könnte ebenfalls heiß sein."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Schritte zur Fehlerbehebung ansehen"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Linke Verknüpfung"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Rechte Verknüpfung"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2a1403c..9fb2726 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Ενεργό"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Απενεργοποίηση"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Μη διαθέσιμο"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"μάθετε περισσότερα"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Γραμμή πλοήγησης"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Διάταξη"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Επιπλέον τύπος αριστερού κουμπιού"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Ορισμένες λειτουργίες περιορίζονται κατά τη μείωση της θερμοκρασίας.\nΠατήστε για περισσότερες πληροφορίες."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Το τηλέφωνό σας θα προσπαθήσει να μειώσει αυτόματα τη θερμοκρασία. Μπορείτε να εξακολουθήσετε να το χρησιμοποιείτε, αλλά είναι πιθανό να λειτουργεί πιο αργά.\n\nΜόλις μειωθεί η θερμοκρασία του τηλεφώνου σας, θα λειτουργεί ξανά κανονικά."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Δείτε βήματα αντιμετώπισης."</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Αποσυνδέστε τη συσκευή"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Η συσκευή έχει αρχίσει να ζεσταίνεται κοντά στη θύρα φόρτισης. Αν είναι συνδεδεμένη σε φορτιστή ή αξεσουάρ USB, αποσυνδέστε την και προσέξτε γιατί και το καλώδιο μπορεί να έχει ζεσταθεί."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Δείτε βήματα αντιμετώπισης"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Αριστερή συντόμευση"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Δεξιά συντόμευση"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 9a01ac8..144777b 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"más información"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Diseño"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón izquierdo adicional"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Algunas funciones se limitan durante el enfriamiento del teléfono.\nPresiona para obtener más información"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Tu teléfono intentará enfriarse automáticamente. Podrás usarlo, pero es posible que funcione más lento.\n\nUna vez que se haya enfriado, volverá a funcionar correctamente."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa el dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"El puerto de carga del dispositivo se está calentando. Si está conectado a un cargador o accesorio USB, desenchúfalo con cuidado, ya que el cable también puede estar caliente."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantenimiento"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Acceso directo izquierdo"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Acceso directo derecho"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 038c7b5..1ba2dc6 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"No disponible"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"más información"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Diseño"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón a la izquierda extra"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Se han limitado algunas funciones mientras el teléfono se enfría.\nToca para ver más información"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"El teléfono intentará enfriarse. Puedes seguir utilizándolo, pero es posible que funcione con mayor lentitud.\n\nUna vez que se haya enfriado, funcionará con normalidad."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa tu dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Tu dispositivo se está calentando cerca del puerto de carga. Si está conectado a un cargador o a un accesorio USB, desenchúfalo con cuidado, ya que el cable también puede estar caliente."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantenimiento"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Acceso directo a la izquierda"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Acceso directo a la derecha"</string>
diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml
index 8d5c3c6..d7a8133 100644
--- a/packages/SystemUI/res/values-es/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml
@@ -58,12 +58,12 @@
   </string-array>
   <string-array name="tile_states_flashlight">
     <item msgid="3465257127433353857">"No disponible"</item>
-    <item msgid="5044688398303285224">"Desactivada"</item>
+    <item msgid="5044688398303285224">"Desactivado"</item>
     <item msgid="8527389108867454098">"Activado"</item>
   </string-array>
   <string-array name="tile_states_rotation">
     <item msgid="4578491772376121579">"No disponible"</item>
-    <item msgid="5776427577477729185">"Desactivada"</item>
+    <item msgid="5776427577477729185">"Desactivado"</item>
     <item msgid="7105052717007227415">"Activado"</item>
   </string-array>
   <string-array name="tile_states_bt">
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 7317404..6c27708 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Sees"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Väljas"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Pole saadaval"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"lisateave"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigeerimisriba"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Paigutus"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Täiendava vasaku nupu tüüp"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Mõned funktsioonid on piiratud, kuni telefon jahtub.\nPuudutage lisateabe saamiseks."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Teie telefon proovib automaatselt maha jahtuda. Saate telefoni ikka kasutada, kuid see võib olla aeglasem.\n\nKui telefon on jahtunud, töötab see tavapäraselt."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vaadake hooldusjuhiseid"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Eemaldage seade"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Teie seade läheb laadimispordi juurest soojaks. Kui see on ühendatud laadija või USB-tarvikuga, eemaldage see ja olge ettevaatlik, kuna kaabel võib samuti soe olla."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vaadake hooldusjuhiseid"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vasak otsetee"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Parem otsetee"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index a761098..2e2d6b5 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Eginbide batzuk ezingo dira erabili telefonoa hoztu arte.\nInformazio gehiago lortzeko, sakatu hau."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonoa automatikoki saiatuko da hozten. Hoztu bitartean, telefonoa erabiltzen jarrai dezakezu, baina mantsoago funtziona lezake.\n\nTelefonoaren tenperatura jaitsi bezain laster, ohi bezala funtzionatzen jarraituko du."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ikusi zaintzeko urratsak"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deskonektatu gailua"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Gailua berotzen ari da kargatzeko atakaren inguruan. Kargagailu edo USB bidezko osagarri batera konektatuta badago, deskonekta ezazu kontuz, kablea ere beroa egongo baita agian."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ikusi zaintzeko urratsak"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ezkerreko lasterbidea"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Eskuineko lasterbidea"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index e7aefda..d2b3f7c 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"روشن"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"خاموش"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"در دسترس نیست"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"بیشتر بدانید"</string>
     <string name="nav_bar" msgid="4642708685386136807">"نوار پیمایش"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"طرح‌بندی"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"نوع دکمه منتهی‌الیه چپ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"وقتی تلفن درحال خنک شدن است، بعضی از ویژگی‌ها محدود می‌شوند.\nبرای اطلاعات بیشتر ضربه بزنید"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"تلفنتان به‌طور خودکار سعی می‌کند خنک شود. همچنان می‌توانید از تلفنتان استفاده کنید، اما ممکن است کندتر عمل کند.\n\nوقتی تلفن خنک شد، عملکرد عادی‌اش از سرگرفته می‌شود."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"دیدن اقدامات محافظتی"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"دستگاه را جدا کنید"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"‏دستگاهتان کنار درگاه شارژ گرم شده است. اگر دستگاهتان به شارژر یا لوازم جانبی USB متصل است، آن را جدا کنید و مراقب باشید چون ممکن است کابل هم گرم باشد."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"مشاهده مراحل احتیاط"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"میان‌بر چپ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"میان‌بر راست"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 21c8dfc..6817033 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Päällä"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Pois päältä"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Ei käytettävissä"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"lukeaksesi lisää"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigointipalkki"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Asettelu"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ylimääräinen vasen painiketyyppi"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Joidenkin ominaisuuksien käyttöä on rajoitettu puhelimen jäähtymisen aikana.\nLue lisää napauttamalla"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Puhelimesi yrittää automaattisesti jäähdyttää itsensä. Voit silti käyttää puhelinta, mutta se voi toimia hitaammin.\n\nKun puhelin on jäähtynyt, se toimii normaalisti."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Katso huoltovaiheet"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Irrota laite"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Laite lämpenee latausportin lähellä. Jos laite on yhdistetty laturiin tai USB-lisälaitteeseen, irrota se varoen, sillä johtokin voi olla lämmin."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Katso huoltovaiheet"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vasen pikakuvake"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Oikea pikakuvake"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index b3439ae..9a3e2d1 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Certaines fonctionnalités sont limitées pendant que le téléphone refroidit.\nTouchez pour en savoir plus"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Votre téléphone va essayer de se refroidir automatiquement. Vous pouvez toujours l\'utiliser, mais il risque d\'être plus lent.\n\nUne fois refroidi, il fonctionnera normalement."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débranchez votre appareil"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Votre appareil chauffe près du port de recharge. S\'il est connecté à un chargeur ou à un accessoire USB, débranchez-le en faisant attention : le câble pourrait également être chaud."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Afficher les étapes d\'entretien"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Raccourci gauche"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Raccourci droit"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 2ffb389..df5ded3 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Activé"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Désactivé"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Indisponible"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"en savoir plus"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barre de navigation"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Disposition"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Type de bouton gauche supplémentaire"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Fonctionnalités limitées pendant le refroidissement du téléphone.\nAppuyer pour en savoir plus"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Votre téléphone va essayer de se refroidir automatiquement. Vous pouvez toujours l\'utiliser, mais il risque d\'être plus lent.\n\nUne fois refroidi, il fonctionnera normalement."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Débrancher votre appareil"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Votre appareil se réchauffe près du port de recharge. S\'il est connecté à un chargeur ou un accessoire USB, débranchez-le en faisant attention, car le câble peut lui aussi être chaud."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Afficher les étapes d\'entretien"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Raccourci gauche"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Raccourci droit"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 2da351a..390742035 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Activado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desactivado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Non dispoñible"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"obter máis información"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegación"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Deseño"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botón adicional á esquerda"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"O uso dalgunhas funcións é limitado mentres o teléfono arrefría.\nToca para obter máis información"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"O teléfono tentará arrefriar automaticamente. Podes utilizalo, pero é probable que funcione máis lento.\n\nUnha vez que arrefríe, funcionará con normalidade."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantemento"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconectar o dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"O dispositivo estase quentando cerca do porto de carga. Se está conectado a un cargador ou a un accesorio USB, desconéctao con coidado, xa que o cable tamén podería estar quente."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver pasos de mantemento"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atallo á esquerda"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atallo á dereita"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 97fed23..b362597 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ફોન ઠંડો થાય ત્યાં સુધી અમુક સુવિધાઓ મર્યાદિત હોય છે.\nવધુ માહિતી માટે ટૅપ કરો"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"તમારો ફોન ઑટોમૅટિક રીતે ઠંડો થવાનો પ્રયાસ કરશે. તમે હજી પણ તમારા ફોનનો ઉપયોગ કરી શકો છો, પરંતુ તે કદાચ થોડો ધીમો ચાલે.\n\nતમારો ફોન ઠંડો થઈ જવા પર, તે સામાન્ય રીતે ચાલશે."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"સારસંભાળના પગલાં જુઓ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"તમારા ડિવાઇસને અનપ્લગ કરો"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"તમારું ડિવાઇસ ચાર્જિંગ પોર્ટની પાસે ગરમ થઈ રહ્યું છે. જો તે કોઈ ચાર્જર અથવા USB ઍક્સેસરી સાથે કનેક્ટેડ હોય, તો તેને અનપ્લગ કરો અને ધ્યાન રાખો, કારણ કે કેબલ ગરમ પણ હોઈ શકે છે."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"સારસંભાળના પગલાં જુઓ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ડાબો શૉર્ટકટ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"જમણો શૉર્ટકટ"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 81a11b4..4d9860a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"चालू"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"बंद"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध नहीं है"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ज़्यादा जानें"</string>
     <string name="nav_bar" msgid="4642708685386136807">"नेविगेशन बार"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"कुछ और बाएं बटन के प्रकार"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"फ़ोन के ठंडा होने तक कुछ सुविधाएं काम नहीं करतीं.\nज़्यादा जानकारी के लिए टैप करें"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"आपका फ़ोन अपने आप ठंडा होने की कोशिश करेगा. आप अब भी अपने फ़ोन का उपयोग कर सकते हैं, लेकिन हो सकता है कि यह धीमी गति से चले.\n\nठंडा हो जाने पर आपका फ़ोन सामान्य रूप से चलेगा."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिवाइस के रखरखाव के तरीके देखें"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिवाइस को अनप्लग करें"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"आपका डिवाइस चार्जिंग पोर्ट के पास गर्म हो रहा है. अगर डिवाइस चार्जर या यूएसबी ऐक्सेसरी से कनेक्ट है, तो उसे अनप्लग करें. साथ ही, ध्यान रखें कि केबल भी गर्म हो सकती है."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"प्रबंधन से जुड़े चरण देखें"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"बायां शॉर्टकट"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"दायां शॉर्टकट"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 7ef30b6..b5221487 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Uključeno"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Isključeno"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nedostupno"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saznajte više"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigacijska traka"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Izgled"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnog lijevog gumba"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Neke su značajke ograničene dok se telefon ne ohladi.\nDodirnite za više informacija"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon će se automatski pokušati ohladiti. Možete ga nastaviti koristiti, no mogao bi raditi sporije.\n\nKad se ohladi, radit će normalno."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pročitajte upute za održavanje"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Vaš se uređaj zagrijava u blizini priključka za punjenje. Ako je priključen u punjač ili USB uređaj, iskopčajte ga. Pazite jer se i kabel možda zagrijao."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Pogledajte upute za održavanje"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lijevi prečac"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desni prečac"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index b6bfc1b..e7efd9d 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Be"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Ki"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nem használható"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"további információ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigációs sáv"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Elrendezés"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"További bal oldali gombtípus"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Bizonyos funkciók korlátozottan működnek a telefon lehűlése közben.\nKoppintson, ha további információra van szüksége."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"A telefon automatikusan megpróbál lehűlni. Továbbra is tudja használni a telefont, de elképzelhető, hogy működése lelassul.\n\nAmint a telefon lehűl, újra a szokásos módon működik majd."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Olvassa el a kímélő használat lépéseit"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Húzza ki az eszközt"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Eszköze kezd melegedni a töltőport közelében. Ha töltő vagy USB-s kiegészítő van csatlakoztatva hozzá, húzza ki, és legyen óvatos, mert a kábel is meleg lehet."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Olvassa el a megfelelő használat lépéseit"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Bal oldali parancsikon"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Jobb oldali parancsikon"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 0380303..804f350 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Միացված է"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Անջատված է"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Հասանելի չէ"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Իմանալ ավելին"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Նավիգացիայի գոտի"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Դասավորություն"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Լրացուցիչ ձախ կոճակի տեսակ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Հովանալու ընթացքում հեռախոսի որոշ գործառույթներ սահմանափակ են։\nՀպեք՝ ավելին իմանալու համար։"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ձեր հեռախոսն ավտոմատ կերպով կփորձի hովանալ: Կարող եք շարունակել օգտագործել հեռախոսը, սակայն հնարավոր է, որ այն ավելի դանդաղ աշխատի:\n\nՀովանալուց հետո հեռախոսը կաշխատի կանոնավոր կերպով:"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Անջատեք սարքը"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Լիցքավորման միացքի հատվածում սարքը տաքանում է։ Եթե լիցքավորիչի կամ USB լրասարքի է միացված սարքը, անջատեք այն և զգույշ եղեք, քանի որ մալուխը ևս կարող է տաքացած լինել։"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ձախ դյուրանցում"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Աջ դյուրանցում"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 163dd9f..da8b3c0 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Aktif"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Nonaktif"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Tidak tersedia"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"pelajari lebih lanjut"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Bilah navigasi"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Tata Letak"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Jenis tombol ekstra kiri"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Beberapa fitur dibatasi saat ponsel mendingin.\nKetuk untuk info selengkapnya"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ponsel akan otomatis mencoba mendingin. Anda tetap dapat menggunakan ponsel, tetapi mungkin berjalan lebih lambat.\n\nSetelah dingin, ponsel akan berjalan seperti biasa."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah-langkah perawatan"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut perangkat"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Perangkat menjadi panas saat di dekat port pengisi daya. Jika perangkat terhubung ke pengisi daya atau aksesori USB, cabutlah dan berhati-hatilah karena suhu kabel mungkin juga panas."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Lihat langkah-langkah perawatan"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Pintasan kiri"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pintasan kanan"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index f2a0304..7f18abf 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Kveikt"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Slökkt"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Ekki í boði"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"nánar"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Yfirlitsstika"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Útlit"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Gerð aukahnapps til vinstri"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Sumir eiginleikar eru takmarkaðir meðan síminn kælir sig.\nÝttu til að fá frekari upplýsingar"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Síminn reynir sjálfkrafa að kæla sig. Þú getur enn notað símann en hann gæti verið hægvirkari.\n\nEftir að síminn hefur kælt sig niður virkar hann eðlilega."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sjá varúðarskref"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Taktu tækið úr sambandi"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Tækið er að hitna nálægt hleðslutenginu. Ef það er tengt við hleðslutæki eða USB-aukahlut skaltu taka það úr sambandi og hafa í huga að snúran gæti einnig verið heit."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Sjá varúðarskref"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Flýtilykill til vinstri"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Flýtilykill til hægri"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 74f3e47..83c58bc 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"On"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Off"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Non disponibile"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"scopri di più"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra di navigazione"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo di pulsante extra sinistra"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Alcune funzionalità limitate durante il raffreddamento del telefono.\nTocca per ulteriori informazioni"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Il telefono cercherà automaticamente di raffreddarsi. Puoi comunque usarlo, ma potrebbe essere più lento.\n\nUna volta raffreddato, il telefono funzionerà normalmente."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Leggi le misure da adottare"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Scollega il dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Il tuo dispositivo si sta scaldando vicino alla porta di ricarica. Se è collegato a un caricabatterie o a un accessorio USB, scollegalo e fai attenzione perché il cavo potrebbe essere caldo."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Leggi le misure da adottare"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Scorciatoia sinistra"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Scorciatoia destra"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 4e0d5ce..09d5d69 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ჩართული"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"გამორთვა"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"მიუწვდომელი"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"შეიტყვეთ მეტი"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ნავიგაციის ზოლი"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"განლაგება"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"მარცხენა დამატებითი ღილაკის ტიპი"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ზოგიერთი ფუნქცია შეზღუდული იქნება, სანამ ტელეფონი გაგრილდება.\nშეეხეთ დამატებითი ინფორმაციის მისაღებად"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"თქვენი ტელეფონი გაგრილებას ავტომატურად შეეცდება. შეგიძლიათ გააგრძელოთ მისით სარგებლობა, თუმცა ტელეფონმა შეიძლება უფრო ნელა იმუშაოს.\n\nგაგრილების შემდგომ ის ჩვეულებრივად იმუშავებს."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"მისაღები ზომების გაცნობა"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"გამოაერᲗეᲗ Თქვენი მოწყობილობა"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"თქვენი მოწყობილობა ხურდება დამტენის პორტთან ახლოს. თუ ის დაკავშირებულია დამტენთან ან USB აქსესუართან, გამორთეთ იგი და იზრუნეთ, რადგან შესაძლოა კაბელიც გახურებული იყოს."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"მისაღები ზომების გაცნობა"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"მარცხენა მალსახმობი"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"მარჯვენა მალსახმობი"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 10c47e3..1183e381 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -161,10 +161,8 @@
     <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Бет танылмады. Орнына саусақ ізін пайдаланыңыз."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <!-- no translation found for keyguard_face_failed (9044619102286917151) -->
-    <skip />
-    <!-- no translation found for keyguard_suggest_fingerprint (8742015961962702960) -->
-    <skip />
+    <string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
+    <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string>
@@ -198,9 +196,9 @@
     <string name="accessibility_quick_settings_less_time" msgid="9110364286464977870">"Азырақ уақыт."</string>
     <string name="accessibility_casting_turned_off" msgid="1387906158563374962">"Экранды трансляциялау тоқтатылды."</string>
     <string name="accessibility_brightness" msgid="5391187016177823721">"Дисплей жарықтығы"</string>
-    <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Мобильдік деректер кідіртілді"</string>
+    <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Мобильдік интернет кідіртілді"</string>
     <string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Деректер кідіртілді"</string>
-    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"Белгіленген деректер шегіне жеттіңіз. Мобильдік деректер енді пайдаланылмайды.\n\nЕгер жалғастырсаңыз, деректер трафигі үшін ақы алынуы мүмкін."</string>
+    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"Белгіленген деректер шегіне жеттіңіз. Мобильдік интернет енді пайдаланылмайды.\n\nЕгер жалғастырсаңыз, деректер трафигі үшін ақы алынуы мүмкін."</string>
     <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Жалғастыру"</string>
     <string name="accessibility_location_active" msgid="2845747916764660369">"Орын өтініштері қосылған"</string>
     <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Датчиктер өшірулі."</string>
@@ -257,7 +255,7 @@
     <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# құрылғы}other{# құрылғы}}"</string>
     <string name="quick_settings_flashlight_label" msgid="4904634272006284185">"Қалта шам"</string>
     <string name="quick_settings_flashlight_camera_in_use" msgid="4820591564526512571">"Камера қолданылып жатыр"</string>
-    <string name="quick_settings_cellular_detail_title" msgid="792977203299358893">"Мобильдік деректер"</string>
+    <string name="quick_settings_cellular_detail_title" msgid="792977203299358893">"Мобильдік интернет"</string>
     <string name="quick_settings_cellular_detail_data_usage" msgid="6105969068871138427">"Дерек шығыны"</string>
     <string name="quick_settings_cellular_detail_remaining_data" msgid="1136599216568805644">"Қалған деректер"</string>
     <string name="quick_settings_cellular_detail_over_limit" msgid="4561921367680636235">"Шектен асу"</string>
@@ -600,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Қосулы"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Өшірулі"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Қолжетімді емес"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"толығырақ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Шарлау тақтасы"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Формат"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Қосымша сол жақ түйме түрі"</string>
@@ -674,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Телефон толық суығанға дейін, кейбір функциялардың жұмысы шектеледі.\nТолығырақ ақпарат үшін түртіңіз."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефон автоматты түрде суи бастайды. Оны пайдалана бере аласыз, бірақ ол баяуырақ жұмыс істеуі мүмкін.\n\nТелефон суығаннан кейін, оның жұмысы қалпына келеді."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Пайдалану нұсқаулығын қараңыз"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Құрылғыны ажыратыңыз"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Құрылғының зарядтау ұяшығы тұрған бөлігі қызып келеді. Зарядтағышқа немесе USB құрылғысына жалғанған болса, оны ажыратыңыз. Абайлаңыз, кабель де ыстық болуы мүмкін."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Пайдалану нұсқаулығын қараңыз"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Сол жақ таңбаша"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Оң жақ таңбаша"</string>
@@ -717,7 +712,7 @@
     <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"Мазаламау режимі автоматты ереже немесе қолданба арқылы қосылды."</string>
     <string name="running_foreground_services_title" msgid="5137313173431186685">"Фонда жұмыс істеп тұрған қолданбалар"</string>
     <string name="running_foreground_services_msg" msgid="3009459259222695385">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string>
-    <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік деректер өшірілсін бе?"</string>
+    <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string>
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 7ee2a93..c30c2a2 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"បើក"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"បិទ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"មិនអាចប្រើបាន"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ស្វែងយល់​បន្ថែម"</string>
     <string name="nav_bar" msgid="4642708685386136807">"របាររុករក"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ប្លង់"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ប្រភេទ​ប៊ូតុង​ខាង​ឆ្វេង​បន្ថែម"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"មុខងារ​មួយចំនួន​នឹងមិនអាច​ប្រើបានពេញលេញ​នោះទេ ខណៈពេល​ដែលទូរសព្ទ​កំពុងបញ្ចុះកម្ដៅ។\nសូមចុច​ដើម្បីទទួលបាន​ព័ត៌មានបន្ថែម"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ទូរសព្ទ​របស់អ្នក​នឹង​ព្យាយាម​បញ្ចុះ​កម្តៅ​ដោយ​ស្វ័យប្រវត្តិ។ អ្នក​នៅតែ​អាច​ប្រើ​ទូរសព្ទ​របស់អ្នក​បាន​ដដែល​ ប៉ុន្តែ​វា​នឹង​ដំណើរ​ការ​យឺត​ជាង​មុន។\n\nបន្ទាប់​ពី​ទូរសព្ទ​របស់អ្នក​ត្រជាក់​ជាង​មុន​ហើយ វា​នឹង​ដំណើរការ​ដូច​ធម្មតា។"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"មើលជំហាន​ថែទាំ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ដកឧបករណ៍របស់អ្នក"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ឧបករណ៍របស់អ្នកកំពុងឡើងកម្ដៅនៅជិតរន្ធសាកថ្ម។ ប្រសិនបើឧបករណ៍នេះត្រូវបានភ្ជាប់ទៅឆ្នាំង​សាក ឬគ្រឿងបរិក្ខារ USB សូមដកវា និងមានការប្រុងប្រយ័ត្ន ដោយសារខ្សែក៏អាចក្ដៅផងដែរ។"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"មើលជំហាន​ថែទាំ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ផ្លូវកាត់​ខាង​ឆ្វេង"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ផ្លូវកាត់​ខាង​ស្តាំ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 2aec2e3..f32d00a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ಆನ್"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ಆಫ್"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ಲಭ್ಯವಿಲ್ಲ"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ನ್ಯಾವಿಗೇಷನ್ ಬಾರ್"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ಲೇಔಟ್"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ಹೆಚ್ಚುವರಿ ಎಡ ಬಟನ್ ವಿಧ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ಫೋನ್ ತಣ್ಣಗಾಗುವವರೆಗೂ ಕೆಲವು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಸೀಮಿತಗೊಳಿಸಲಾಗುತ್ತದೆ\nಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ನಿಮ್ಮ ಫೋನ್ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ತಣ್ಣಗಾಗಲು ಪ್ರಯತ್ನಿಸುತ್ತದೆ. ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೀವು ಈಗಲೂ ಬಳಸಬಹುದಾಗಿರುತ್ತದೆ, ಆದರೆ ಇದು ನಿಧಾನವಾಗಿರಬಹುದು.\n\nಒಮ್ಮೆ ನಿಮ್ಮ ಫೋನ್ ತಣ್ಣಗಾದ ನಂತರ ಇದು ಸಾಮಾನ್ಯ ರೀತಿಯಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್‌ಪ್ಲಗ್ ಮಾಡಿ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ಚಾರ್ಜಿಂಗ್ ಪೋರ್ಟ್ ಸಮೀಪದಲ್ಲಿ ನಿಮ್ಮ ಸಾಧನವು ಬಿಸಿಯಾಗುತ್ತಿದೆ. ಅದನ್ನು ಚಾರ್ಜರ್ ಅಥವಾ USB ಪರಿಕರಕ್ಕೆ ಕನೆಕ್ಟ್ ಮಾಡಿದ್ದರೆ, ಅದನ್ನು ಅನ್‌ಪ್ಲಗ್ ಮಾಡಿ ಹಾಗೂ ಕೇಬಲ್ ಕೂಡ ಬಿಸಿಯಾಗಿರಬಹುದು ಆದ್ದರಿಂದ ಎಚ್ಚರಿಕೆ ವಹಿಸಿ."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ಕಾಳಜಿ ಹಂತಗಳನ್ನು ನೋಡಿ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ಎಡ ಶಾರ್ಟ್‌ಕಟ್"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ಬಲ ಶಾರ್ಟ್‌ಕಟ್"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index b33d766..72e837b 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"사용"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"사용 안함"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"사용할 수 없음"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"자세히 알아보기"</string>
     <string name="nav_bar" msgid="4642708685386136807">"탐색 메뉴"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"레이아웃"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"추가 왼쪽 버튼 유형"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"휴대전화 온도를 낮추는 동안 일부 기능이 제한됩니다.\n자세히 알아보려면 탭하세요."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"휴대전화 온도를 자동으로 낮추려고 시도합니다. 휴대전화를 계속 사용할 수는 있지만 작동이 느려질 수도 있습니다.\n\n휴대전화 온도가 낮아지면 정상적으로 작동됩니다."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"해결 방법 확인하기"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"기기 분리하기"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"기기의 충전 포트 주변 온도가 상승하고 있습니다. 충전기나 USB 액세서리가 연결된 상태라면 분리하세요. 이때 케이블도 뜨거울 수 있으므로 주의하시기 바랍니다."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"취해야 할 조치 확인"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"왼쪽 바로가기"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"오른쪽 바로가기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b352277..e6e3818 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Күйүк"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Өчүк"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Жеткиликсиз"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"кеңири маалымат"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Чабыттоо тилкеси"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Калып"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Сол жактагы кошумча баскычтын түрү"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Телефон сууганча айрым элементтердин иши чектелген.\nКеңири маалымат алуу үчүн таптап коюңуз"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонуңуз автоматтык түрдө сууйт. Аны колдоно берсеңиз болот, бирок ал жайыраак иштеп калат.\n\nТелефонуңуз суугандан кийин адаттагыдай эле иштеп баштайт."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Тейлөө кадамдарын көрүңүз"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Түзмөктү сууруп коюңуз"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Түзмөгүңүздүн кубаттоо порту жылып баратат. Эгер түзмөгүңүз кубаттагычка же USB кабелине туташып турса, аны сууруп коюңуз. Абайлаңыз, кабель да жылуу болушу мүмкүн."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Тейлөө кадамдарын көрүңүз"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Сол жактагы кыска жол"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Оң жактагы кыска жол"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index f90e869..5a97ca5 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ເປີດ"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ປິດ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ບໍ່ສາມາດໃຊ້ໄດ້"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ສຶກສາເພີ່ມເຕີມ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ແຖບນຳທາງ"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ຮູບແບບ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ປະເພດປຸ່ມຊ້າຍພິເສດ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ຄຸນສົມບັດບາງຢ່າງຖືກຈຳກັດໄວ້ໃນເວລາຫຼຸດອຸນຫະພູມຂອງໂທລະສັບ.\nແຕະເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ໂທລະສັບຂອງທ່ານຈະພະຍາຍາມລົດອຸນຫະພູມລົງ. ທ່ານຍັງຄົງສາມາດໃຊ້ໂທລະສັບຂອງທ່ານໄດ້ຢູ່, ແຕ່ມັນຈະເຮັດວຽກຊ້າລົງ.\n\nເມື່ອໂທລະສັບຂອງທ່ານບໍ່ຮ້ອນຫຼາຍແລ້ວ, ມັນຈະກັບມາເຮັດວຽກຕາມປົກກະຕິ."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ຖອດອຸປະກອນຂອງທ່ານອອກ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ອຸປະກອນຂອງທ່ານຈະອຸ່ນຂຶ້ນເມື່ອຢູ່ໃກ້ຊ່ອງສາກໄຟ. ຫາກມັນເຊື່ອມຕໍ່ຫາສາຍສາກ ຫຼື ອຸປະກອນເສີມ USB ໃດໜຶ່ງຢູ່, ໃຫ້ຖອດມັນອອກ ແລະ ລະວັງເນື່ອງຈາກສາຍກໍອາດຈະອຸ່ນເຊັ່ນກັນ."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ປຸ່ມລັດຊ້າຍ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ປຸ່ມລັດຂວາ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index fdf4570..0e641c7 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Įjungta"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Išjungta"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nepasiekiama"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"sužinoti daugiau"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Naršymo juosta"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Išdėstymas"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Papildomo mygtuko kairėje tipas"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Kai kurios funkcijos gali neveikti, kol telefonas vėsta.\nPalietę gausite daugiau informacijos"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonas automatiškai bandys atvėsti. Telefoną vis tiek galėsite naudoti, tačiau jis gali veikti lėčiau.\n\nKai telefonas atvės, jis veiks įprastai."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Žr. priežiūros veiksmus"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atjunkite įrenginį"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Įrenginys kaista šalia įkrovimo prievado. Jei jis prijungtas prie kroviklio ar USB priedo, atjunkite jį ir patikrinkite, nes laidas taip pat gali būti įkaitęs."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Žr. priežiūros veiksmus"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Spartusis klavišas kairėje"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Spartusis klavišas dešinėje"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 9ca7da5..07ae2c9 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Ieslēgts"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Izslēgts"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nav pieejams"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"uzzinātu vairāk"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigācijas josla"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Izkārtojums"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Kreisās puses papildu pogas veids"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Dažas funkcijas ir ierobežotas, kamēr notiek tālruņa atdzišana.\nPieskarieties, lai uzzinātu vairāk."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Jūsu tālrunis automātiski mēģinās atdzist. Jūs joprojām varat izmantot tālruni, taču tas, iespējams, darbosies lēnāk.\n\nTiklīdz tālrunis būs atdzisis, tas darbosies normāli."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Skatīt apkopes norādījumus"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Atvienojiet savu ierīci"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Jūsu ierīce uzkarst, atrodoties uzlādes pieslēgvietas tuvumā. Ja ierīce ir pievienota lādētājam vai USB piederumam, uzmanīgi atvienojiet to, jo arī vads var būt uzkarsis."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Skatīt apkopes norādījumus"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Saīsne kreisajā pusē"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Saīsne labajā pusē"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index a241e54..dc12d09 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Некои функции се ограничени додека телефонот се лади.\nДопрете за повеќе информации"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефонот автоматски ќе се обиде да се олади. Вие сепак ќе може да го користите, но тој може да работи побавно.\n\nОткако ќе се олади, ќе работи нормално."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Прикажи ги чекорите за грижа за уредот"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Исклучете го уредот од кабел"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Вашиот уред се загрева во близина на портата за полнење. Ако е поврзан со полнач или USB-додаток, исклучете го од него и внимавајте бидејќи кабелот може да е топол."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Прикажи ги чекорите за грижа за уредот"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Лева кратенка"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десна кратенка"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 4ff23018..17fa9ea 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ഓൺ"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ഓഫ്"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ലഭ്യമല്ല"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"കൂടുതലറിയുക"</string>
     <string name="nav_bar" msgid="4642708685386136807">"നാവിഗേഷൻ ബാർ"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ലേ‌ഔട്ട്"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"അധിക ഇടത് ബട്ടൺ തരം"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ഫോൺ തണുത്തുകൊണ്ടിരിക്കുമ്പോൾ ചില ഫീച്ചറുകൾ പരിമിതപ്പെടുത്തപ്പെടും.\nകൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പ് ചെയ്യുക"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"നിങ്ങളുടെ ഫോൺ സ്വയമേവ തണുക്കാൻ ശ്രമിക്കും. നിങ്ങൾക്ക് അപ്പോഴും ഫോൺ ഉപയോഗിക്കാമെങ്കിലും പ്രവർത്തനം മന്ദഗതിയിലായിരിക്കും.\n\nതണുത്തുകഴിഞ്ഞാൽ, ഫോൺ സാധാരണ ഗതിയിൽ പ്രവർത്തിക്കും."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ഉപകരണം അൺപ്ലഗ് ചെയ്യുക"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ചാർജിംഗ് പോർട്ടിന് സമീപം നിങ്ങളുടെ ഉപകരണം ചൂടാകുന്നുണ്ട്. ഇത് ചാർജറിലേക്കോ USB ആക്‌സസറിയിലേക്കോ കണക്‌റ്റ് ചെയ്‌തിട്ടുണ്ടെങ്കിൽ അൺപ്ലഗ് ചെയ്യുക, കേബിളും ചൂടായിരിക്കാമെന്നതിനാൽ ശ്രദ്ധിക്കണം."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"മുൻകരുതൽ നടപടികൾ കാണുക"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ഇടത് കുറുക്കുവഴി"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"വലത് കുറുക്കുവഴി"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index a3d13bd..751ef192 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Утсыг хөрөх үед зарим онцлогийг хязгаарлана.\nДэлгэрэнгүй мэдээлэл авах бол товшино уу"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Таны утас автоматаар хөрөх болно. Та утсаа ашиглаж болох хэдий ч удаан ажиллаж болзошгүй.\n\nТаны утас хөрсний дараагаар хэвийн ажиллана."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Хянамж болгоомжийн алхмыг харах"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Төхөөрөмжөө салгана уу"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Таны төхөөрөмж цэнэглэх портын ойролцоо халж байна. Хэрэв төхөөрөмжийг цэнэглэгч эсвэл USB дагалдах хэрэгсэлд холбосон бол төхөөрөмжийг салгаж, кабель нь халуун байж болзошгүй тул болгоомжтой байгаарай."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Хянамж болгоомжийн алхмыг харна уу"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Зүүн товчлол"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Баруун товчлол"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c9f3bc2..ab8d952 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -327,9 +327,9 @@
     <string name="do_disclosure_generic" msgid="4896482821974707167">"हे डिव्हाइस तुमच्या संस्थेचे आहे"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"हे डिव्हाइस <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> चे आहे"</string>
     <string name="do_financed_disclosure_with_name" msgid="6723004643314467864">"हे डिव्हाइस <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> द्वारे पुरवले गेले आहे"</string>
-    <string name="phone_hint" msgid="6682125338461375925">"फोनसाठी चिन्हावरून स्वाइप करा"</string>
-    <string name="voice_hint" msgid="7476017460191291417">"व्हॉइस सहाय्यासाठी चिन्हावरून स्वाइप करा"</string>
-    <string name="camera_hint" msgid="4519495795000658637">"कॅमेर्‍यासाठी चिन्हावरून स्वाइप करा"</string>
+    <string name="phone_hint" msgid="6682125338461375925">"फोनसाठी आयकनवरून स्वाइप करा"</string>
+    <string name="voice_hint" msgid="7476017460191291417">"व्हॉइस सहाय्यासाठी आयकनवरून स्वाइप करा"</string>
+    <string name="camera_hint" msgid="4519495795000658637">"कॅमेर्‍यासाठी आयकनवरून स्वाइप करा"</string>
     <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"संपूर्ण शांतता. हे स्क्रीन रीडर ना देखील शांत करेल."</string>
     <string name="interruption_level_none" msgid="219484038314193379">"संपूर्ण शांतता"</string>
     <string name="interruption_level_priority" msgid="661294280016622209">"केवळ प्राधान्य"</string>
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"सुरू"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"बंद"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध नाही"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"अधिक जाणून घ्या"</string>
     <string name="nav_bar" msgid="4642708685386136807">"नॅव्हिगेशन बार"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"अतिरिक्त डाव्या बटणाचा प्रकार"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"फोन थंड होईपर्यंत काही वैशिष्ट्ये मर्यादित केली.\nअधिक माहितीसाठी टॅप करा"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"तुमचा फोन स्वयंचलितपणे थंड होईल. तुम्ही अद्यापही तुमचा फोन वापरू शकता परंतु तो कदाचित धीमेपणे कार्य करेल.\n\nतुमचा फोन एकदा थंड झाला की, तो सामान्यपणे कार्य करेल."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"काय काळजी घ्यावी ते पहा"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"तुमचे डिव्हाइस अनप्लग करा"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"तुमचे डिव्हाइस हे चार्जिंग पोर्टच्या जवळ गरम होत आहे. हे चार्जर किंवा USB अ‍ॅक्सेसरी यांच्याशी कनेक्ट केलेले असल्यास, ते अनप्लग करा आणि काळजी घ्या कारण केबलदेखील गरम असू शकते."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"काय काळजी घ्यावी ते पहा"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"डावा शॉर्टकट"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"उजवा शॉर्टकट"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b161345..11b788c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Hidup"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Mati"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Tidak tersedia"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"mengetahui lebih lanjut"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Bar navigasi"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Reka letak"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Jenis butang kiri tambahan"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Sesetengah ciri adalah terhad semasa telefon menyejuk.\nKetik untuk mendapatkan maklumat lanjut"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon anda akan cuba menyejuk secara automatik. Anda masih dapat menggunakan telefon itu tetapi telefon tersebut mungkin berjalan lebih perlahan.\n\nSetelah telefon anda sejuk, telefon itu akan berjalan seperti biasa."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah penjagaan"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut palam peranti anda"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Peranti anda menjadi panas berdekatan port pengecasan. Jika peranti anda disambungkan ke pengecas atau aksesori USB, cabut palam peranti dan berhati-hati kerana kabel juga mungkin panas."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Lihat langkah penjagaan"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Pintasan kiri"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pintasan kanan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 3f201ac3..e87be58 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ဖွင့်"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ပိတ်"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"မရနိုင်ပါ"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ပိုမိုလေ့လာရန်"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ရွှေ့လျားရန်ဘားတန်း"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"အပြင်အဆင်"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"လက်ဝဲခလုတ် အမျိုးအစားအပို"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ဖုန်းကို အေးအောင်ပြုလုပ်နေစဉ်တွင် အချို့ဝန်ဆောင်မှုများကို ကန့်သတ်ထားပါသည်။\nနောက်ထပ်အချက်အလက်များအတွက် တို့ပါ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"သင့်ဖုန်းသည် အလိုအလျောက် ပြန်အေးသွားပါလိမ့်မည်။ ဖုန်းကို အသုံးပြုနိုင်ပါသေးသည် သို့သော် ပိုနှေးနိုင်ပါသည်။\n\nသင့်ဖုန်း အေးသွားသည်နှင့် ပုံမှန်အတိုင်း ပြန်အလုပ်လုပ်ပါလိမ့်မည်။"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"သင့်စက်ကို ပလတ်ဖြုတ်ပါ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"သင့်စက်သည် အားသွင်းပို့တ်အနီးတွင် ပူနွေးလာသည်။ ၎င်းကို အားသွင်းကိရိယာ (သို့) USB ဆက်စပ်ပစ္စည်းနှင့် ချိတ်ဆက်ထားပါက ပလတ်ဖြုတ်ပါ။ ကြိုးကလည်း ပူနွေးနေနိုင်သဖြင့် ဂရုပြုပါ။"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"လက်ဝဲ ဖြတ်လမ်းလင့်ခ်"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"လက်ယာ ဖြတ်လမ်းလင့်ခ်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 24dcef7..22f3836 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"På"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Av"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Utilgjengelig"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"finne ut mer"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigasjonsrad"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Oppsett"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra venstre-knapptype"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Enkelte funksjoner er begrenset mens telefonen kjøles ned.\nTrykk for å se mer informasjon"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonen din kommer til å prøve å kjøle seg ned automatisk. Du kan fremdeles bruke telefonen, men den kjører muligens saktere.\n\nTelefonen kommer til å kjøre som normalt, når den har kjølt seg ned."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se vedlikeholdstrinnene"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koble fra enheten"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Enheten begynner å bli varm nær ladeporten. Hvis den er koblet til en lader eller et USB-tilbehør, må du koble den fra. Vær forsiktig da kabelen også kan være varm."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Se vedlikeholdstrinnene"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Venstre hurtigtast"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Høyre hurtigtast"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 4f85e3e..eb78765 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"अन छ"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"अफ छ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"उपलब्ध छैन"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"थप जान्नुहोस्"</string>
     <string name="nav_bar" msgid="4642708685386136807">"नेभिगेशन पट्टी"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"लेआउट"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"अतिरिक्त बायाँतिरको बटनको प्रकार"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"फोन नचिस्सिँदासम्म केही सुविधाहरू उपलब्ध हुने छैनन्।\nथप जानकारीका लागि ट्याप गर्नुहोस्"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"तपाईंको फोन स्वतः चिसो हुने प्रयास गर्ने छ। तपाईं अझै पनि आफ्नो फोनको प्रयोग गर्न सक्नुहुन्छ तर त्यो अझ ढिलो चल्न सक्छ।\n\nचिसो भएपछि तपाईंको फोन सामान्य गतिमा चल्नेछ।"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"डिभाइस बिजुलीको स्रोतबाट निकाल्नुहोस्"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"तपाईंको डिभाइसको चार्जिङ पोर्टतिरको भाग तातीरहेको छ। तपाईंको डिभाइस चार्जर वा USB एक्सेसरीमा जोडिएको गरिएको छ भने त्यसलाई निकाल्नुहोस्। यसका साथै सो केबल तातो हुन सक्ने भएकाले ख्याल गर्नुहोला।"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"हेरचाहसम्बन्धी चरणहरू हेर्नुहोस्‌"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"बायाँतिरको सर्टकट"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"दायाँतिरको सर्टकट"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index ee08b01..c6c63b5 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Bepaalde functies zijn beperkt terwijl de telefoon afkoelt.\nTik voor meer informatie"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Je telefoon probeert automatisch af te koelen. Je kunt je telefoon nog steeds gebruiken, maar deze kan langzamer werken.\n\nZodra de telefoon is afgekoeld, werkt deze weer normaal."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Onderhoudsstappen bekijken"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Je apparaat loskoppelen"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Je apparaat wordt warm in de buurt van de oplaadpoort. Als het apparaat is aangesloten op een oplader of USB-poort, koppel je het los. Wees voorzichtig: de kabel kan warm zijn."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Onderhoudsstappen bekijken"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Snelkoppeling links"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Snelkoppeling rechts"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 6923145..d6f742c 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -178,7 +178,7 @@
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ସମସ୍ତ ବିଜ୍ଞପ୍ତି ଦେଖନ୍ତୁ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ଟେଲି-ଟାଇପରାଇଟର୍ ସକ୍ଷମ ଅଛି।"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ରିଙ୍ଗର୍‌ କମ୍ପନରେ ଅଛି।"</string>
-    <string name="accessibility_ringer_silent" msgid="8994620163934249882">"ରିଙ୍ଗର୍‌ ସାଇଲେଣ୍ଟରେ ଅଛି।"</string>
+    <string name="accessibility_ringer_silent" msgid="8994620163934249882">"ରିଂଗର ସାଇଲେଣ୍ଟରେ ଅଛି।"</string>
     <!-- no translation found for accessibility_casting (8708751252897282313) -->
     <skip />
     <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"ବିଜ୍ଞପ୍ତି ଶେଡ୍‍।"</string>
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ଚାଲୁ ଅଛି"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ବନ୍ଦ ଅଛି"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ଅନୁପଲବ୍ଧ"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ନାଭିଗେଶନ୍ ବାର୍‍"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ଲେଆଉଟ୍"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ସମ୍ପୂର୍ଣ୍ଣ ବାମ ବଟନ୍‍ ପ୍ରକାର"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ଫୋନ୍ ଥଣ୍ଡା ହେବା ସମୟରେ କିଛି ଫିଚର୍ ଠିକ ଭାବେ କାମ କରିନଥାଏ।\nଅଧିକ ସୂଚନା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ଆପଣଙ୍କ ଫୋନ୍‍ ସ୍ୱଚାଳିତ ଭାବେ ଥଣ୍ଡା ହେବାକୁ ଚେଷ୍ଟା କରିବ। ଆପଣ ତଥାପି ନିଜ ଫୋନ୍‍ ବ୍ୟବହାର କରିପାରିବେ, କିନ୍ତୁ ଏହା ଧୀରେ ଚାଲିପାରେ।\n\nଆପଣଙ୍କ ଫୋନ୍‍ ଥଣ୍ଡା ହୋଇଯିବାପରେ, ଏହା ସାମାନ୍ୟ ଭାବେ ଚାଲିବ।"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନପ୍ଲଗ କରନ୍ତୁ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ଚାର୍ଜିଂ ପୋର୍ଟ ନିକଟରେ ଆପଣଙ୍କ ଡିଭାଇସ ଗରମ ହୋଇଯାଉଛି। ଯଦି ଏହା ଏକ ଚାର୍ଜର କିମ୍ବା USB ଆକସେସୋରୀ ସହ କନେକ୍ଟ କରାଯାଇଥାଏ ତେବେ ଏହାକୁ ଅନପ୍ଲଗ କରନ୍ତୁ ଏବଂ ଧ୍ୟାନ ରଖନ୍ତୁ କାରଣ କେବୁଲ ମଧ୍ୟ ଗରମ ହୋଇପାରେ।"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ସେବା ସମ୍ବନ୍ଧିତ ଷ୍ଟେପ୍‌ଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ବାମ ଶର୍ଟକଟ୍‍"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ଡାହାଣ ଶର୍ଟକଟ୍‍"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 2beeb59..df36b54 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ਚਾਲੂ"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ਬੰਦ"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ਅਣਉਪਲਬਧ"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ਹੋਰ ਜਾਣੋ"</string>
     <string name="nav_bar" msgid="4642708685386136807">"ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"ਖਾਕਾ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ਵਧੇਰੇ ਖੱਬੇ ਬਟਨ ਕਿਸਮ"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ਫ਼ੋਨ ਦੇ ਠੰਡਾ ਹੋਣ ਦੇ ਦੌਰਾਨ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਸੀਮਤ ਹੁੰਦੀਆਂ ਹਨ।\nਵਧੇਰੇ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ਤੁਹਾਡਾ ਫ਼ੋਨ ਸਵੈਚਲਿਤ ਰੂਪ ਵਿੱਚ ਠੰਡਾ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੇਗਾ। ਤੁਸੀਂ ਹਾਲੇ ਵੀ ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਵਰਤ ਸਕਦੇ ਹੋ, ਪਰੰਤੂ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਵਧੇਰੇ ਹੌਲੀ ਚੱਲੇ।\n\nਇੱਕ ਵਾਰ ਠੰਡਾ ਹੋਣ ਤੋਂ ਬਾਅਦ ਤੁਹਾਡਾ ਫ਼ੋਨ ਸਧਾਰਨ ਤੌਰ \'ਤੇ ਚੱਲੇਗਾ।"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ਆਪਣਾ ਡੀਵਾਈਸ ਅਣਪਲੱਗ ਕਰੋ"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਚਾਰਜਿੰਗ ਪੋਰਟ ਦੇ ਨੇੜੇ ਗਰਮ ਹੋ ਰਿਹਾ ਹੈ। ਜੇ ਇਹ ਕਿਸੇ ਚਾਰਜਰ ਜਾਂ USB ਐਕਸੈਸਰੀ ਨਾਲ ਕਨੈਕਟ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਅਣਪਲੱਗ ਕਰੋ ਅਤੇ ਸਾਵਧਾਨ ਰਹੋ, ਕਿਉਂਕਿ ਕੇਬਲ ਵੀ ਗਰਮ ਹੋ ਸਕਦੀ ਹੈ।"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ਖੱਬਾ ਸ਼ਾਰਟਕੱਟ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ਸੱਜਾ ਸ਼ਾਰਟਕੱਟ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 9449f26..f66eff3 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Włączono"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Wyłączono"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Niedostępne"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"Więcej informacji"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Pasek nawigacji"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Układ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Typ dodatkowego lewego przycisku"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Podczas obniżania temperatury telefonu niektóre funkcje są ograniczone\nKliknij, by dowiedzieć się więcej"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon automatycznie podejmie próbę obniżenia temperatury. Możesz go wciąż używać, ale telefon może działać wolniej.\n\nGdy temperatura się obniży, telefon będzie działał normalnie."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobacz instrukcję postępowania"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odłącz urządzenie"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Urządzenie za bardzo się nagrzewa w okolicy gniazda ładowania. Jeśli jest podłączone do ładowarki albo akcesorium USB, odłącz je. Uważaj, bo kabel również może być nagrzany."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobacz instrukcję postępowania"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lewy skrót"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Prawy skrót"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 2f9a36e..6ce4ccb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo extra"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Alguns recursos ficam limitados enquanto o smartphone é resfriado.\nToque para saber mais"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Seu smartphone tentará se resfriar automaticamente. Você ainda poderá usá-lo, mas talvez ele fique mais lento.\n\nQuando o smartphone estiver resfriado, ele voltará ao normal."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Seu dispositivo está ficando quente perto da porta de carregamento. Desconecte qualquer carregador ou acessório USB que esteja conectado, mas tome cuidado, porque o cabo também pode estar quente."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver etapas de cuidado"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atalho à esquerda"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atalho à direita"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 3251eec..37918d4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Esquema"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo adicional"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 2f9a36e..6ce4ccb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Ativado"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Desativado"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Indisponível"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"saber mais"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barra de navegação"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tipo de botão esquerdo extra"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Alguns recursos ficam limitados enquanto o smartphone é resfriado.\nToque para saber mais"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Seu smartphone tentará se resfriar automaticamente. Você ainda poderá usá-lo, mas talvez ele fique mais lento.\n\nQuando o smartphone estiver resfriado, ele voltará ao normal."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Seu dispositivo está ficando quente perto da porta de carregamento. Desconecte qualquer carregador ou acessório USB que esteja conectado, mas tome cuidado, porque o cabo também pode estar quente."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Ver etapas de cuidado"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Atalho à esquerda"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Atalho à direita"</string>
diff --git a/packages/SystemUI/res/values-ro-ldrtl/strings.xml b/packages/SystemUI/res/values-ro-ldrtl/strings.xml
index e167b41..a7cd33c 100644
--- a/packages/SystemUI/res/values-ro-ldrtl/strings.xml
+++ b/packages/SystemUI/res/values-ro-ldrtl/strings.xml
@@ -19,5 +19,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recents_quick_scrub_onboarding" msgid="2452671841151577157">"Trageți spre stânga pentru a comuta rapid între aplicații"</string>
+    <string name="recents_quick_scrub_onboarding" msgid="2452671841151577157">"Trage spre stânga pentru a comuta rapid între aplicații"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 683774c..01f7e18 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -20,53 +20,53 @@
 <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="4811759950673118541">"UI sistem"</string>
-    <string name="battery_low_title" msgid="5319680173344341779">"Activați Economisirea bateriei?"</string>
+    <string name="battery_low_title" msgid="5319680173344341779">"Activezi Economisirea bateriei?"</string>
     <string name="battery_low_description" msgid="3282977755476423966">"Mai aveți <xliff:g id="PERCENTAGE">%s</xliff:g> din baterie. Economisirea bateriei activează Tema întunecată, restricționează activitatea în fundal și amână notificările."</string>
     <string name="battery_low_intro" msgid="5148725009653088790">"Economisirea bateriei activează Tema întunecată, restricționează activitatea în fundal și amână notificările."</string>
     <string name="battery_low_percent_format" msgid="4276661262843170964">"Procent rămas din baterie: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="invalid_charger_title" msgid="938685362320735167">"Nu se poate realiza încărcarea prin USB"</string>
-    <string name="invalid_charger_text" msgid="2339310107232691577">"Folosiți încărcătorul livrat împreună cu dispozitivul"</string>
-    <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activați economisirea bateriei?"</string>
+    <string name="invalid_charger_text" msgid="2339310107232691577">"Folosește încărcătorul livrat împreună cu dispozitivul"</string>
+    <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activezi economisirea bateriei?"</string>
     <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Despre Economisirea bateriei"</string>
-    <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activați"</string>
-    <string name="battery_saver_start_action" msgid="8353766979886287140">"Activați"</string>
+    <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activează"</string>
+    <string name="battery_saver_start_action" msgid="8353766979886287140">"Activează"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nu, mulțumesc"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotire automată a ecranului"</string>
-    <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiteți <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
-    <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiteți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
-    <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Permiteți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
-    <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
+    <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"Permisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB. Dacă folosiți <xliff:g id="APPLICATION">%1$s</xliff:g> cu acest dispozitiv, acest lucru vă poate împiedica să auziți apeluri, notificări și alarme."</string>
     <string name="usb_audio_device_prompt" msgid="7944987408206252949">"Dacă folosiți <xliff:g id="APPLICATION">%1$s</xliff:g> cu acest dispozitiv, acest lucru vă poate împiedica să auziți apeluri, notificări și alarme."</string>
-    <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"Permiteți <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
-    <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
-    <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> pentru a gestiona <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
-    <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"Deschideți <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
-    <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"Aplic. instal. nu funcț. cu acest acces. USB. Aflați despre acest accesoriu la <xliff:g id="URL">%1$s</xliff:g>"</string>
+    <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> pentru a gestiona <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
+    <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"Deschizi <xliff:g id="APPLICATION">%1$s</xliff:g> ca să gestioneze <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"Aplic. instal. nu funcț. cu acest acces. USB. Află despre acest accesoriu la <xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="1236358027511638648">"Accesoriu USB"</string>
-    <string name="label_view" msgid="6815442985276363364">"Afișați"</string>
-    <string name="always_use_device" msgid="210535878779644679">"Deschideți întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
-    <string name="always_use_accessory" msgid="1977225429341838444">"Deschideți întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
-    <string name="usb_debugging_title" msgid="8274884945238642726">"Permiteți remedierea erorilor prin USB?"</string>
+    <string name="label_view" msgid="6815442985276363364">"Afișează"</string>
+    <string name="always_use_device" msgid="210535878779644679">"Deschide întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
+    <string name="always_use_accessory" msgid="1977225429341838444">"Deschide întotdeauna <xliff:g id="APPLICATION">%1$s</xliff:g> când este conectat <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
+    <string name="usb_debugging_title" msgid="8274884945238642726">"Permiți remedierea erorilor prin USB?"</string>
     <string name="usb_debugging_message" msgid="5794616114463921773">"Amprenta din cheia RSA a computerului este:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
-    <string name="usb_debugging_always" msgid="4003121804294739548">"Permiteți întotdeauna de pe acest computer"</string>
-    <string name="usb_debugging_allow" msgid="1722643858015321328">"Permiteți"</string>
+    <string name="usb_debugging_always" msgid="4003121804294739548">"Permite întotdeauna de pe acest computer"</string>
+    <string name="usb_debugging_allow" msgid="1722643858015321328">"Permite"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Remedierea erorilor prin USB nu este permisă"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comutați la utilizatorul principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comută la utilizatorul principal."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Schimbați limba de sistem la <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Alt dispozitiv solicită schimbarea limbii de sistem"</string>
-    <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Schimbați limba"</string>
-    <string name="hdmi_cec_set_menu_language_decline" msgid="7650721096558646011">"Păstrați limba actuală"</string>
-    <string name="wifi_debugging_title" msgid="7300007687492186076">"Permiteți remedierea erorilor wireless în această rețea?"</string>
+    <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Schimbă limba"</string>
+    <string name="hdmi_cec_set_menu_language_decline" msgid="7650721096558646011">"Păstrează limba actuală"</string>
+    <string name="wifi_debugging_title" msgid="7300007687492186076">"Permiți remedierea erorilor wireless în această rețea?"</string>
     <string name="wifi_debugging_message" msgid="5461204211731802995">"Numele rețelei (SSID)\n<xliff:g id="SSID_0">%1$s</xliff:g>\n\nAdresa Wi‑Fi (BSSID)\n<xliff:g id="BSSID_1">%2$s</xliff:g>"</string>
-    <string name="wifi_debugging_always" msgid="2968383799517975155">"Permiteți întotdeauna în această rețea"</string>
-    <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permiteți"</string>
+    <string name="wifi_debugging_always" msgid="2968383799517975155">"Permite întotdeauna în această rețea"</string>
+    <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permite"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Remedierea erorilor wireless nu este permisă"</string>
     <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor wireless. Pentru a folosi această funcție, comutați la utilizatorul principal."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Portul USB a fost dezactivat"</string>
-    <string name="usb_contaminant_message" msgid="7730476585174719805">"Pentru a vă proteja dispozitivul de lichide sau reziduuri, portul USB este dezactivat și nu va detecta niciun accesoriu.\n\nVeți primi o notificare când puteți folosi din nou portul USB."</string>
+    <string name="usb_contaminant_message" msgid="7730476585174719805">"Pentru a proteja dispozitivul de lichide sau reziduuri, portul USB este dezactivat și nu va detecta niciun accesoriu.\n\nVei primi o notificare când poți folosi din nou portul USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Portul USB a fost activat pentru a detecta încărcătoarele și accesoriile"</string>
-    <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Activați USB"</string>
+    <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Activează USB"</string>
     <string name="learn_more" msgid="4690632085667273811">"Mai multe"</string>
     <string name="global_action_screenshot" msgid="2760267567509131654">"Captură de ecran"</string>
     <string name="global_action_smart_lock_disabled" msgid="9097102067802412936">"Smart Lock dezactivat"</string>
@@ -75,15 +75,15 @@
     <string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string>
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblocați dispozitivul"</string>
-    <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încercați să faceți din nou o captură de ecran"</string>
+    <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string>
     <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string>
-    <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Crearea capturilor de ecran nu este permisă de aplicație sau de organizația dvs."</string>
+    <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Crearea capturilor de ecran nu e permisă de aplicație sau de organizația ta"</string>
     <string name="screenshot_blocked_by_admin" msgid="5486757604822795797">"Administratorul IT a blocat crearea capturilor de ecran"</string>
-    <string name="screenshot_edit_label" msgid="8754981973544133050">"Editați"</string>
-    <string name="screenshot_edit_description" msgid="3333092254706788906">"Editați captura de ecran"</string>
-    <string name="screenshot_share_description" msgid="2861628935812656612">"Trimiteți captura de ecran"</string>
+    <string name="screenshot_edit_label" msgid="8754981973544133050">"Editează"</string>
+    <string name="screenshot_edit_description" msgid="3333092254706788906">"Editează captura de ecran"</string>
+    <string name="screenshot_share_description" msgid="2861628935812656612">"Trimite captura de ecran"</string>
     <string name="screenshot_scroll_label" msgid="2930198809899329367">"Surprindeți mai mult"</string>
-    <string name="screenshot_dismiss_description" msgid="4702341245899508786">"Închideți captura de ecran"</string>
+    <string name="screenshot_dismiss_description" msgid="4702341245899508786">"Închide captura de ecran"</string>
     <string name="screenshot_preview_description" msgid="7606510140714080474">"Previzualizare a capturii de ecran"</string>
     <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Marginea de sus la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marginea de jos la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
@@ -102,50 +102,50 @@
     <string name="screenrecord_start" msgid="330991441575775004">"Începeți"</string>
     <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Se înregistrează ecranul"</string>
     <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Se înregistrează ecranul și conținutul audio"</string>
-    <string name="screenrecord_taps_label" msgid="1595690528298857649">"Afișați atingerile de pe ecran"</string>
-    <string name="screenrecord_stop_label" msgid="72699670052087989">"Opriți"</string>
-    <string name="screenrecord_share_label" msgid="5025590804030086930">"Trimiteți"</string>
+    <string name="screenrecord_taps_label" msgid="1595690528298857649">"Afișează atingerile de pe ecran"</string>
+    <string name="screenrecord_stop_label" msgid="72699670052087989">"Oprește"</string>
+    <string name="screenrecord_share_label" msgid="5025590804030086930">"Trimite"</string>
     <string name="screenrecord_save_title" msgid="1886652605520893850">"Înregistrarea ecranului a fost salvată"</string>
-    <string name="screenrecord_save_text" msgid="3008973099800840163">"Atingeți pentru a afișa"</string>
+    <string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Eroare la ștergerea înregistrării ecranului"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</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>
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Accesibilitate"</string>
-    <string name="accessibility_rotate_button" msgid="1238584767612362586">"Rotiți ecranul"</string>
+    <string name="accessibility_rotate_button" msgid="1238584767612362586">"Rotește ecranul"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"Recente"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"Cameră foto"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"Telefon"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistent vocal"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofel"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner de coduri QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblocați"</string>
+    <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblochează"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispozitiv blocat"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanarea chipului"</string>
-    <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimiteți"</string>
-    <string name="cancel" msgid="1089011503403416730">"Anulați"</string>
-    <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Confirmați"</string>
-    <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Încercați din nou"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Atingeți pentru a anula autentificarea"</string>
-    <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Încercați din nou"</string>
+    <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimite"</string>
+    <string name="cancel" msgid="1089011503403416730">"Anulează"</string>
+    <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Confirmă"</string>
+    <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Încearcă din nou"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Atinge pentru a anula autentificarea"</string>
+    <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Încearcă din nou"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="3401633342366146535">"Se caută chipul"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="2242167416140740920">"Chip autentificat"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"Confirmat"</string>
-    <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Atingeți Confirmați pentru a finaliza"</string>
-    <string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"S-a deblocat cu ajutorul feței. Apăsați pictograma de deblocare pentru a continua"</string>
-    <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"S-a deblocat cu ajutorul feței. Apăsați pentru a continua."</string>
-    <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Chipul a fost recunoscut. Apăsați pentru a continua."</string>
+    <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Atinge Confirm pentru a finaliza"</string>
+    <string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"S-a deblocat cu ajutorul feței. Apasă pictograma de deblocare pentru a continua"</string>
+    <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"S-a deblocat cu ajutorul feței. Apasă pentru a continua."</string>
+    <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Chipul a fost recunoscut. Apasă pentru a continua."</string>
     <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Chip recunoscut. Apăsați pictograma de deblocare să continuați."</string>
     <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Autentificat"</string>
-    <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Folosiți PIN-ul"</string>
-    <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Folosiți modelul"</string>
-    <string name="biometric_dialog_use_password" msgid="3445033859393474779">"Folosiți parola"</string>
+    <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Folosește PIN-ul"</string>
+    <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Folosește modelul"</string>
+    <string name="biometric_dialog_use_password" msgid="3445033859393474779">"Folosește parola"</string>
     <string name="biometric_dialog_wrong_pin" msgid="1878539073972762803">"PIN greșit"</string>
     <string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"Model greșit"</string>
     <string name="biometric_dialog_wrong_password" msgid="69477929306843790">"Parolă greșită"</string>
-    <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"Prea multe încercări incorecte.\nÎncercați din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string>
-    <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"Încercați din nou. Încercarea <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> din <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>."</string>
+    <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"Prea multe încercări incorecte.\nÎncearcă din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string>
+    <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"Încearcă din nou. Încercarea <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> din <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>."</string>
     <string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"Datele dvs. vor fi șterse"</string>
     <string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"Dacă la următoarea încercare introduceți un model incorect, datele de pe acest dispozitiv vor fi șterse."</string>
     <string name="biometric_dialog_last_pin_attempt_before_wipe_device" msgid="9151756675698215723">"Dacă la următoarea încercare introduceți un cod PIN incorect, datele de pe acest dispozitiv vor fi șterse."</string>
@@ -156,9 +156,9 @@
     <string name="biometric_dialog_last_pattern_attempt_before_wipe_profile" msgid="6045224069529284686">"Dacă la următoarea încercare introduceți un model incorect, profilul de serviciu și datele sale vor fi șterse."</string>
     <string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"Dacă la următoarea încercare introduceți un cod PIN incorect, profilul de serviciu și datele sale vor fi șterse."</string>
     <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"Dacă la următoarea încercare introduceți o parolă incorectă, profilul de serviciu și datele sale vor fi șterse."</string>
-    <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atingeți senzorul de amprente"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atinge senzorul de amprente"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Pictograma amprentă"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosiți amprenta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosește amprenta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
@@ -175,7 +175,7 @@
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterie: <xliff:g id="NUMBER">%d</xliff:g> la sută."</string>
     <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Procentul rămas din baterie este <xliff:g id="PERCENTAGE">%1$s</xliff:g>. În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria se încarcă, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> la sută."</string>
-    <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vedeți toate notificările"</string>
+    <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vezi toate notificările"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter activat."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrare sonerie."</string>
     <string name="accessibility_ringer_silent" msgid="8994620163934249882">"Sonerie silențioasă."</string>
@@ -185,7 +185,7 @@
     <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Setări rapide."</string>
     <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Ecranul de blocare."</string>
     <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Ecran de blocare pentru serviciu"</string>
-    <string name="accessibility_desc_close" msgid="8293708213442107755">"Închideți"</string>
+    <string name="accessibility_desc_close" msgid="8293708213442107755">"Închide"</string>
     <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"niciun sunet"</string>
     <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"numai alarme"</string>
     <string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"Nu deranja."</string>
@@ -198,11 +198,11 @@
     <string name="accessibility_brightness" msgid="5391187016177823721">"Luminozitatea ecranului"</string>
     <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Datele mobile sunt întrerupte"</string>
     <string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Conexiunea de date este întreruptă"</string>
-    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"A fost atinsă limita de date setată. Datele mobile nu mai sunt folosite.\n\nDacă reluați, este posibil să se aplice taxe pentru utilizarea datelor."</string>
-    <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Reluați"</string>
+    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"A fost atinsă limita de date setată. Datele mobile nu mai sunt folosite.\n\nDacă reiei, se pot aplica taxe pentru utilizarea datelor."</string>
+    <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Reia"</string>
     <string name="accessibility_location_active" msgid="2845747916764660369">"Solicitări locație active"</string>
     <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Dezactivarea senzorilor este activă"</string>
-    <string name="accessibility_clear_all" msgid="970525598287244592">"Ștergeți toate notificările."</string>
+    <string name="accessibility_clear_all" msgid="970525598287244592">"Șterge toate notificările."</string>
     <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="notification_group_overflow_description" msgid="7176322877233433278">"{count,plural, =1{Încă # notificare în grup.}few{Încă # notificări în grup.}other{Încă # de notificări în grup.}}"</string>
     <string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"Ecranul este blocat în orientarea de tip peisaj."</string>
@@ -245,7 +245,7 @@
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
     <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string>
-    <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închideți"</string>
+    <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închide"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectat"</string>
     <string name="quick_settings_connected_battery_level" msgid="1322075669498906959">"Conectat, bateria la <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="quick_settings_connecting" msgid="2381969772953268809">"Se conectează..."</string>
@@ -281,7 +281,7 @@
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Serviciul NFC este activat"</string>
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Înregistrarea ecranului"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Începeți"</string>
-    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Opriți"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Oprește"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Modul cu o mână"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblocați microfonul dispozitivului?"</string>
     <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblocați camera dispozitivului?"</string>
@@ -299,21 +299,21 @@
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cameră foto disponibilă"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfon și cameră disponibile"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Alt dispozitiv"</string>
-    <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comutați secțiunea Recente"</string>
-    <string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de dvs. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string>
-    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Se vor anunța prin sunete și vibrații numai alarmele. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string>
-    <string name="zen_priority_customize_button" msgid="4119213187257195047">"Personalizați"</string>
-    <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor. Totuși, veți putea iniția apeluri."</string>
+    <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comută secțiunea Recente"</string>
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de tine. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"Se vor anunța prin sunete și vibrații numai alarmele. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string>
+    <string name="zen_priority_customize_button" msgid="4119213187257195047">"Personalizează"</string>
+    <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor. Totuși, vei putea iniția apeluri."</string>
     <string name="zen_silence_introduction" msgid="6117517737057344014">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor."</string>
-    <string name="notification_tap_again" msgid="4477318164947497249">"Atingeți din nou pentru a deschide"</string>
-    <string name="tap_again" msgid="1315420114387908655">"Atingeți din nou"</string>
-    <string name="keyguard_unlock" msgid="8031975796351361601">"Glisați în sus pentru a deschide"</string>
-    <string name="keyguard_unlock_press" msgid="9140109453735019209">"Apăsați pictograma de deblocare pentru a deschide"</string>
+    <string name="notification_tap_again" msgid="4477318164947497249">"Atinge din nou pentru a deschide"</string>
+    <string name="tap_again" msgid="1315420114387908655">"Atinge din nou"</string>
+    <string name="keyguard_unlock" msgid="8031975796351361601">"Glisează în sus pentru a deschide"</string>
+    <string name="keyguard_unlock_press" msgid="9140109453735019209">"Apasă pictograma de deblocare pentru a deschide"</string>
     <string name="keyguard_face_successful_unlock_swipe" msgid="6180997591385846073">"S-a deblocat folosind fața. Glisați în sus și deschideți."</string>
-    <string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"S-a deblocat cu ajutorul feței. Apăsați pictograma de deblocare pentru a deschide"</string>
-    <string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"S-a deblocat cu ajutorul feței. Apăsați pentru a deschide."</string>
-    <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Chipul a fost recunoscut. Apăsați pentru a deschide."</string>
-    <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Chip recunoscut. Apăsați pictograma de deblocare pentru a deschide"</string>
+    <string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"S-a deblocat cu ajutorul feței. Apasă pictograma de deblocare pentru a deschide"</string>
+    <string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"S-a deblocat cu ajutorul feței. Apasă pentru a deschide."</string>
+    <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Chipul a fost recunoscut. Apasă pentru a deschide."</string>
+    <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Chip recunoscut. Apasă pictograma de deblocare pentru a deschide"</string>
     <string name="keyguard_face_successful_unlock" msgid="4203999851465708287">"S-a deblocat folosind fața"</string>
     <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Chipul a fost recunoscut"</string>
   <string-array name="udfps_accessibility_touch_hints">
@@ -322,14 +322,14 @@
     <item msgid="4844142668312841831">"Deplasați spre dreapta"</item>
     <item msgid="5640521437931460125">"Deplasați în sus"</item>
   </string-array>
-    <string name="keyguard_retry" msgid="886802522584053523">"Glisați pentru a încerca din nou"</string>
-    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Deblocați pentru a folosi NFC"</string>
+    <string name="keyguard_retry" msgid="886802522584053523">"Glisează pentru a încerca din nou"</string>
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Deblochează pentru a folosi NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Dispozitivul aparține organizației dvs."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="do_financed_disclosure_with_name" msgid="6723004643314467864">"Acest dispozitiv este oferit de <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
-    <string name="phone_hint" msgid="6682125338461375925">"Glisați dinspre telefon"</string>
-    <string name="voice_hint" msgid="7476017460191291417">"Glisați dinspre pictogramă pentru asistentul vocal"</string>
-    <string name="camera_hint" msgid="4519495795000658637">"Glisați pentru a fotografia"</string>
+    <string name="phone_hint" msgid="6682125338461375925">"Glisează dinspre telefon"</string>
+    <string name="voice_hint" msgid="7476017460191291417">"Glisează dinspre pictogramă pentru asistentul vocal"</string>
+    <string name="camera_hint" msgid="4519495795000658637">"Glisează pentru a fotografia"</string>
     <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"Liniște absolută. Se va opri sunetul și pentru cititoarele de ecran."</string>
     <string name="interruption_level_none" msgid="219484038314193379">"Niciun sunet"</string>
     <string name="interruption_level_priority" msgid="661294280016622209">"Numai cu prioritate"</string>
@@ -342,35 +342,35 @@
     <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
-    <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Comutați între utilizatori"</string>
+    <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
-    <string name="guest_wipe_session_title" msgid="7147965814683990944">"Bine ați revenit în sesiunea pentru invitați!"</string>
-    <string name="guest_wipe_session_message" msgid="3393823610257065457">"Vreți să continuați sesiunea?"</string>
-    <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Începeți din nou"</string>
-    <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Da, continuați"</string>
+    <string name="guest_wipe_session_title" msgid="7147965814683990944">"Bine ai revenit în sesiunea pentru invitați!"</string>
+    <string name="guest_wipe_session_message" msgid="3393823610257065457">"Continui sesiunea?"</string>
+    <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Începe din nou"</string>
+    <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Da, continuă"</string>
     <string name="guest_notification_app_name" msgid="2110425506754205509">"Modul pentru invitați"</string>
-    <string name="guest_notification_session_active" msgid="5567273684713471450">"Folosiți modul pentru invitați"</string>
+    <string name="guest_notification_session_active" msgid="5567273684713471450">"Folosește modul pentru invitați"</string>
     <string name="user_add_user_message_guest_remove" msgid="5589286604543355007">\n\n"Dacă adăugați un utilizator nou, veți ieși din modul pentru invitați și se vor șterge toate aplicațiile și datele din sesiunea pentru invitați actuală."</string>
-    <string name="user_limit_reached_title" msgid="2429229448830346057">"Ați atins limita de utilizatori"</string>
+    <string name="user_limit_reached_title" msgid="2429229448830346057">"Ai atins limita de utilizatori"</string>
     <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{Se poate crea doar un utilizator.}few{Puteți adăuga până la # utilizatori.}other{Puteți adăuga până la # de utilizatori.}}"</string>
-    <string name="user_remove_user_title" msgid="9124124694835811874">"Eliminați utilizatorul?"</string>
+    <string name="user_remove_user_title" msgid="9124124694835811874">"Elimini utilizatorul?"</string>
     <string name="user_remove_user_message" msgid="6702834122128031833">"Toate aplicațiile și datele acestui utilizator vor fi șterse."</string>
-    <string name="user_remove_user_remove" msgid="8387386066949061256">"Eliminați"</string>
+    <string name="user_remove_user_remove" msgid="8387386066949061256">"Elimină"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începeți să înregistrați sau să proiectați?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Începeți să înregistrați sau să proiectați cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <string name="clear_all_notifications_text" msgid="348312370303046130">"Ștergeți toate notificările"</string>
-    <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionați"</string>
+    <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
+    <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Noi"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silențioase"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificări"</string>
     <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversații"</string>
-    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ștergeți toate notificările silențioase"</string>
+    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Șterge toate notificările silențioase"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificări întrerupte prin „Nu deranja”"</string>
-    <string name="media_projection_action_text" msgid="3634906766918186440">"Începeți acum"</string>
+    <string name="media_projection_action_text" msgid="3634906766918186440">"Începe acum"</string>
     <string name="empty_shade_text" msgid="8935967157319717412">"Nicio notificare"</string>
     <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Dispozitivul este gestionat de unul dintre părinți"</string>
     <string name="quick_settings_disclosure_management_monitoring" msgid="8231336875820702180">"Organizația dvs. deține acest dispozitiv și poate monitoriza traficul de rețea"</string>
@@ -382,8 +382,8 @@
     <string name="quick_settings_disclosure_named_management" msgid="3476472755775165827">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>"</string>
     <string name="quick_settings_disclosure_management_vpns" msgid="929181757984262902">"Acest dispozitiv aparține organizației dvs. și este conectat la internet prin rețele VPN."</string>
     <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> și este conectat la internet prin rețele VPN."</string>
-    <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"Este posibil ca organizația dvs. să monitorizeze traficul de rețea în profilul dvs. de serviciu"</string>
-    <string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"Este posibil ca <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> să monitorizeze traficul de rețea din profilul dvs. de serviciu"</string>
+    <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"E posibil ca organizația ta să monitorizeze traficul de rețea în profilul de serviciu"</string>
+    <string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"E posibil ca <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> să monitorizeze traficul de rețea din profilul tău de serviciu"</string>
     <string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"Adminul IT poate vedea profilul de serviciu"</string>
     <string name="quick_settings_disclosure_monitoring" msgid="8548019955631378680">"Este posibil ca rețeaua să fie monitorizată"</string>
     <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"Acest dispozitiv este conectat la internet prin rețele VPN."</string>
@@ -395,40 +395,40 @@
     <string name="monitoring_subtitle_vpn" msgid="800485258004629079">"VPN"</string>
     <string name="monitoring_subtitle_network_logging" msgid="2444199331891219596">"Înregistrare în jurnal pentru rețea"</string>
     <string name="monitoring_subtitle_ca_certificate" msgid="8588092029755175800">"Certificate CA"</string>
-    <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Afișați politicile"</string>
-    <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Vedeți opțiunile"</string>
+    <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Afișează politicile"</string>
+    <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Vezi opțiunile"</string>
     <string name="monitoring_description_named_management" msgid="505833016545056036">"Dispozitivul aparține organizației <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nAdministratorul dvs. IT poate să monitorizeze și să gestioneze setările, accesul la nivelul companiei, aplicațiile, datele asociate dispozitivului și informațiile despre locația dispozitivului.\n\nPentru mai multe informații, contactați administratorul IT."</string>
     <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"Este posibil ca <xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> să acceseze date asociate dispozitivului, să gestioneze aplicații și să modifice setările acestuia.\n\nDacă aveți întrebări, luați legătura cu <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string>
     <string name="monitoring_description_management" msgid="4308879039175729014">"Dispozitivul aparține organizației dvs.\n\nAdministratorul dvs. IT poate să monitorizeze și să gestioneze setările, accesul la nivelul companiei, aplicațiile, datele asociate dispozitivului și informațiile despre locația dispozitivului.\n\nPentru mai multe informații, contactați administratorul IT."</string>
-    <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Organizația dvs. a instalat un certificat CA pe acest dispozitiv. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string>
-    <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Organizația dvs. a instalat un certificat CA în profilul dvs. de serviciu. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string>
-    <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Pe acest dispozitiv este instalat un certificat CA. Traficul dvs. sigur de rețea poate fi monitorizat sau modificat."</string>
-    <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Administratorul dvs. a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul de pe dispozitivul dvs."</string>
+    <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Organizația ta a instalat un certificat CA pe acest dispozitiv. Traficul de rețea securizat poate fi monitorizat sau modificat."</string>
+    <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Organizația ta a instalat un certificat CA în profilul tău de serviciu. Traficul de rețea securizat poate fi monitorizat sau modificat."</string>
+    <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Pe acest dispozitiv este instalat un certificat CA. Traficul de rețea securizat poate fi monitorizat sau modificat."</string>
+    <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Administratorul tău a activat înregistrarea în jurnal pentru rețea, funcție care monitorizează traficul de pe dispozitivul tău."</string>
     <string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"Administratorul a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul în profilul dvs. de serviciu, dar nu și în profilul personal."</string>
     <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"Acest dispozitiv este conectat la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT."</string>
     <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Acest dispozitiv este conectat la internet prin aplicațiile <xliff:g id="VPN_APP_0">%1$s</xliff:g> și <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT."</string>
     <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Aplicațiile dvs. pentru lucru sunt conectate la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea cu aplicațiile pentru lucru, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru administratorul IT și pentru furnizorul de servicii VPN."</string>
     <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Aplicațiile dvs. personale sunt conectate la internet prin aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>. Activitatea în rețea, inclusiv e-mailurile și datele de navigare, sunt vizibile pentru furnizorul de servicii VPN."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
-    <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Deschideți Setări VPN"</string>
+    <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Deschide Setări VPN"</string>
     <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Dispozitivul este gestionat de unul dintre părinți. Părintele poate să vadă și să gestioneze informații cum ar fi aplicațiile pe care le folosești, locația ta și durata de folosire a dispozitivului."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Deblocat de TrustAgent"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="accessibility_volume_settings" msgid="1458961116951564784">"Setări de sunet"</string>
-    <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Adăugați subtitrări automate la fișierele media"</string>
+    <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Adaugă subtitrări automate la fișierele media"</string>
     <string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"Sfat pentru subtitrări"</string>
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Suprapunere pe subtitrări"</string>
-    <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"activați"</string>
-    <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"dezactivați"</string>
+    <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"activează"</string>
+    <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"dezactivează"</string>
     <string name="sound_settings" msgid="8874581353127418308">"Sunete și vibrații"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Setări"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"Aplicația este fixată"</string>
-    <string name="screen_pinning_description" msgid="8699395373875667743">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Recente pentru a anula fixarea."</string>
-    <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Acasă pentru a anula fixarea."</string>
-    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișată până anulați fixarea. Glisați în sus și țineți apăsat pentru a anula fixarea."</string>
-    <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Recente pentru a anula fixarea."</string>
-    <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Acasă pentru a anula fixarea."</string>
+    <string name="screen_pinning_description" msgid="8699395373875667743">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunile Înapoi și Recente pentru a anula fixarea."</string>
+    <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunile Înapoi și Acasă pentru a anula fixarea."</string>
+    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișată până anulați fixarea. Glisează în sus și ține apăsat pentru a anula fixarea."</string>
+    <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunea Recente pentru a anula fixarea."</string>
+    <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Astfel rămâne afișat până anulezi fixarea. Atinge lung opțiunea Acasă pentru a anula fixarea."</string>
     <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Pot fi accesate date cu caracter personal (cum ar fi agenda și conținutul e-mailurilor)."</string>
     <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Aplicațiile fixate pot deschide alte aplicații."</string>
     <string name="screen_pinning_toast" msgid="8177286912533744328">"Pentru a anula fixarea acestei aplicații, atingeți lung butoanele Înapoi și Recente"</string>
@@ -449,57 +449,57 @@
     <string name="stream_accessibility" msgid="3873610336741987152">"Accesibilitate"</string>
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonerie"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrații"</string>
-    <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blocați"</string>
-    <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atingeți pentru a activa sunetul."</string>
-    <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atingeți pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
-    <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atingeți pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
-    <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atingeți pentru a seta pe vibrații."</string>
-    <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atingeți pentru a dezactiva sunetul."</string>
-    <string name="volume_ringer_change" msgid="3574969197796055532">"Atingeți pentru a schimba modul soneriei"</string>
-    <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivați sunetul"</string>
-    <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activați sunetul"</string>
+    <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blochează"</string>
+    <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atinge pentru a activa sunetul."</string>
+    <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atinge pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
+    <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atinge pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string>
+    <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atinge pentru a seta pe vibrații."</string>
+    <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atinge pentru a dezactiva sunetul."</string>
+    <string name="volume_ringer_change" msgid="3574969197796055532">"Atinge pentru a schimba modul soneriei"</string>
+    <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivează sunetul"</string>
+    <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activează sunetul"</string>
     <string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"vibrații"</string>
     <string name="volume_dialog_title" msgid="6502703403483577940">"Comenzi de volum pentru %s"</string>
     <string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"Apelurile și notificările vor suna (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Bară de stare"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mod demonstrativ pentru IU sistem"</string>
-    <string name="enable_demo_mode" msgid="3180345364745966431">"Activați modul demonstrativ"</string>
-    <string name="show_demo_mode" msgid="3677956462273059726">"Afișați modul demonstrativ"</string>
+    <string name="enable_demo_mode" msgid="3180345364745966431">"Activează modul demonstrativ"</string>
+    <string name="show_demo_mode" msgid="3677956462273059726">"Afișează modul demonstrativ"</string>
     <string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string>
     <string name="status_bar_alarm" msgid="87160847643623352">"Alarmă"</string>
     <string name="wallet_title" msgid="5369767670735827105">"Portofel"</string>
     <string name="wallet_empty_state_label" msgid="7776761245237530394">"Configurați pentru a face achiziții mai rapide și mai sigure cu telefonul dvs."</string>
-    <string name="wallet_app_button_label" msgid="7123784239111190992">"Afișați-le pe toate"</string>
-    <string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Atingeți pentru a deschide"</string>
+    <string name="wallet_app_button_label" msgid="7123784239111190992">"Afișează-le pe toate"</string>
+    <string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Atinge pentru a deschide"</string>
     <string name="wallet_secondary_label_updating" msgid="5726130686114928551">"Se actualizează"</string>
-    <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblocați pentru a folosi"</string>
-    <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încercați din nou mai târziu"</string>
+    <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblochează pentru a folosi"</string>
+    <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încearcă din nou mai târziu"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string>
     <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanați codul QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string>
-    <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu veți auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string>
+    <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu vei auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template" msgid="2234991538018805736">"la <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"Hotspot"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil de serviciu"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Distractiv pentru unii, dar nu pentru toată lumea"</string>
-    <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vă oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuați cu prudență."</string>
-    <string name="tuner_persistent_warning" msgid="230466285569307806">"Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuați cu prudență."</string>
+    <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string>
+    <string name="tuner_persistent_warning" msgid="230466285569307806">"Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string>
     <string name="got_it" msgid="477119182261892069">"Am înțeles"</string>
     <string name="tuner_toast" msgid="3812684836514766951">"Felicitări! System UI Tuner a fost adăugat în Setări"</string>
-    <string name="remove_from_settings" msgid="633775561782209994">"Eliminați din Setări"</string>
-    <string name="remove_from_settings_prompt" msgid="551565437265615426">"Eliminați System UI Tuner din Setări și încetați utilizarea tuturor funcțiilor sale?"</string>
-    <string name="enable_bluetooth_title" msgid="866883307336662596">"Activați Bluetooth?"</string>
-    <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string>
-    <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activați"</string>
+    <string name="remove_from_settings" msgid="633775561782209994">"Elimină din Setări"</string>
+    <string name="remove_from_settings_prompt" msgid="551565437265615426">"Elimini System UI Tuner din Setări și încetezi utilizarea tuturor funcțiilor sale?"</string>
+    <string name="enable_bluetooth_title" msgid="866883307336662596">"Activezi Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activezi Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activează"</string>
     <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Comenzi de gestionare a notificărilor"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activată – În funcție de chip"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, puteți să setați un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string>
+    <string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, poți seta un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gata"</string>
-    <string name="inline_ok_button" msgid="603075490581280343">"Aplicați"</string>
-    <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Dezactivați notificările"</string>
+    <string name="inline_ok_button" msgid="603075490581280343">"Aplică"</string>
+    <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Dezactivează notificările"</string>
     <string name="notification_silence_title" msgid="8608090968400832335">"Silențios"</string>
     <string name="notification_alert_title" msgid="3656229781017543655">"Prestabilite"</string>
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automat"</string>
@@ -523,23 +523,23 @@
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Acest grup de notificări nu poate fi configurat aici"</string>
     <string name="notification_delegate_header" msgid="1264510071031479920">"Notificare prin proxy"</string>
     <string name="notification_channel_dialog_title" msgid="6856514143093200019">"Toate notificările din <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="see_more_title" msgid="7409317011708185729">"Vedeți mai multe"</string>
+    <string name="see_more_title" msgid="7409317011708185729">"Vezi mai multe"</string>
     <string name="feedback_alerted" msgid="5192459808484271208">"Notificarea a fost &lt;b&gt;promovată automat la Prestabilită&lt;/b&gt; de sistem."</string>
     <string name="feedback_silenced" msgid="9116540317466126457">"Notificarea a fost &lt;b&gt;setată automat ca Silențioasă&lt;/b&gt; de sistem."</string>
     <string name="feedback_promoted" msgid="2125562787759780807">"Notificarea a fost &lt;b&gt;clasificată automat mai sus&lt;/b&gt; în umbră."</string>
     <string name="feedback_demoted" msgid="951884763467110604">"Notificarea a fost &lt;b&gt;clasificată automat mai jos&lt;/b&gt; în umbră."</string>
-    <string name="feedback_prompt" msgid="3656728972307896379">"Trimiteți feedback dezvoltatorului. Este corect?"</string>
+    <string name="feedback_prompt" msgid="3656728972307896379">"Trimite feedback dezvoltatorului. Este corect?"</string>
     <string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"Opțiunile privind notificările pentru <xliff:g id="APP_NAME">%1$s</xliff:g> sunt afișate"</string>
     <string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"Opțiunile privind notificările pentru <xliff:g id="APP_NAME">%1$s</xliff:g> nu sunt afișate"</string>
     <string name="notification_more_settings" msgid="4936228656989201793">"Mai multe setări"</string>
-    <string name="notification_app_settings" msgid="8963648463858039377">"Personalizați"</string>
-    <string name="notification_conversation_bubble" msgid="2242180995373949022">"Afișați balonul"</string>
-    <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Eliminați baloanele"</string>
+    <string name="notification_app_settings" msgid="8963648463858039377">"Personalizează"</string>
+    <string name="notification_conversation_bubble" msgid="2242180995373949022">"Afișează balonul"</string>
+    <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Elimină baloanele"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"comenzile notificării"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opțiuni de amânare a notificării"</string>
     <string name="notification_menu_snooze_action" msgid="5415729610393475019">"Reamintește-mi"</string>
-    <string name="snooze_undo" msgid="2738844148845992103">"Anulați"</string>
+    <string name="snooze_undo" msgid="2738844148845992103">"Anulează"</string>
     <string name="snoozed_for_time" msgid="7586689374860469469">"Amânată <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
     <string name="snoozeHourOptions" msgid="2332819756222425558">"{count,plural, =1{# oră}=2{# ore}few{# ore}other{# de ore}}"</string>
     <string name="snoozeMinuteOptions" msgid="2222082405822030979">"{count,plural, =1{# minut}few{# minute}other{# de minute}}"</string>
@@ -556,28 +556,28 @@
     <string name="keyboard_key_space" msgid="6980847564173394012">"Spațiu"</string>
     <string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string>
     <string name="keyboard_key_backspace" msgid="4095278312039628074">"Backspace"</string>
-    <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Redați/Întrerupeți"</string>
-    <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Opriți"</string>
+    <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Redă/Întrerupe"</string>
+    <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Oprește"</string>
     <string name="keyboard_key_media_next" msgid="8502476691227914952">"Înainte"</string>
     <string name="keyboard_key_media_previous" msgid="5637875709190955351">"Înapoi"</string>
-    <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Derulați înapoi"</string>
-    <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Derulați rapid înainte"</string>
+    <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Derulează înapoi"</string>
+    <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Derulează rapid înainte"</string>
     <string name="keyboard_key_page_up" msgid="173914303254199845">"O pagină mai sus"</string>
     <string name="keyboard_key_page_down" msgid="9035902490071829731">"O pagină mai jos"</string>
-    <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Ștergeți"</string>
+    <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Șterge"</string>
     <string name="keyboard_key_move_home" msgid="3496502501803911971">"La început"</string>
     <string name="keyboard_key_move_end" msgid="99190401463834854">"La final"</string>
-    <string name="keyboard_key_insert" msgid="4621692715704410493">"Inserați"</string>
+    <string name="keyboard_key_insert" msgid="4621692715704410493">"Inserează"</string>
     <string name="keyboard_key_num_lock" msgid="7209960042043090548">"Num Lock"</string>
     <string name="keyboard_key_numpad_template" msgid="7316338238459991821">"Tasta numerică <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Eliminați atașamentul"</string>
+    <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Elimină atașamentul"</string>
     <string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"Sistem"</string>
     <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Ecran de pornire"</string>
     <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recente"</string>
     <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Înapoi"</string>
     <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificări"</string>
     <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Comenzi rapide de la tastatură"</string>
-    <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Schimbați aspectul tastaturii"</string>
+    <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Schimbă aspectul tastaturii"</string>
     <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Aplicații"</string>
     <string name="keyboard_shortcut_group_applications_assist" msgid="771606231466098742">"Asistent"</string>
     <string name="keyboard_shortcut_group_applications_browser" msgid="2776211137869809251">"Browser"</string>
@@ -590,16 +590,15 @@
     <string name="volume_dnd_silent" msgid="4154597281458298093">"Comandă rapidă din butoanele de volum"</string>
     <string name="battery" msgid="769686279459897127">"Baterie"</string>
     <string name="headset" msgid="4485892374984466437">"Set căști-microfon"</string>
-    <string name="accessibility_long_click_tile" msgid="210472753156768705">"Deschideți setările"</string>
+    <string name="accessibility_long_click_tile" msgid="210472753156768705">"Deschide setările"</string>
     <string name="accessibility_status_bar_headphones" msgid="1304082414912647414">"Căștile sunt conectate"</string>
     <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Setul căști-microfon este conectat"</string>
     <string name="data_saver" msgid="3484013368530820763">"Economizor de date"</string>
     <string name="accessibility_data_saver_on" msgid="5394743820189757731">"Economizorul de date este activat"</string>
     <string name="switch_bar_on" msgid="1770868129120096114">"Activat"</string>
-    <string name="switch_bar_off" msgid="5669805115416379556">"Dezactivați"</string>
+    <string name="switch_bar_off" msgid="5669805115416379556">"Dezactivează"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Indisponibil"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"a afla mai multe"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Bară de navigare"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Aspect"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Tip de buton din extrema stângă"</string>
@@ -607,7 +606,7 @@
   <string-array name="nav_bar_buttons">
     <item msgid="2681220472659720036">"Clipboard"</item>
     <item msgid="4795049793625565683">"Cod de tastă"</item>
-    <item msgid="80697951177515644">"Confirmați rotirea, comutator de la tastatură"</item>
+    <item msgid="80697951177515644">"Confirmă rotirea, comutator de la tastatură"</item>
     <item msgid="7626977989589303588">"Niciunul"</item>
   </string-array>
   <string-array name="nav_bar_layouts">
@@ -616,19 +615,19 @@
     <item msgid="7453955063378349599">"Înclinat spre stânga"</item>
     <item msgid="5874146774389433072">"Înclinat spre dreapta"</item>
   </string-array>
-    <string name="save" msgid="3392754183673848006">"Salvați"</string>
-    <string name="reset" msgid="8715144064608810383">"Resetați"</string>
+    <string name="save" msgid="3392754183673848006">"Salvează"</string>
+    <string name="reset" msgid="8715144064608810383">"Resetează"</string>
     <string name="clipboard" msgid="8517342737534284617">"Clipboard"</string>
     <string name="accessibility_key" msgid="3471162841552818281">"Buton personalizat pentru navigare"</string>
     <string name="left_keycode" msgid="8211040899126637342">"Codul de taste din stânga"</string>
     <string name="right_keycode" msgid="2480715509844798438">"Codul de taste din dreapta"</string>
     <string name="left_icon" msgid="5036278531966897006">"Pictograma din stânga"</string>
     <string name="right_icon" msgid="1103955040645237425">"Pictograma din dreapta"</string>
-    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Țineți apăsat și trageți pentru a adăuga piese"</string>
-    <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Țineți apăsat și trageți pentru a rearanja piesele"</string>
-    <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Trageți aici pentru a elimina"</string>
-    <string name="drag_to_remove_disabled" msgid="933046987838658850">"Aveți nevoie de cel puțin <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> carduri"</string>
-    <string name="qs_edit" msgid="5583565172803472437">"Editați"</string>
+    <string name="drag_to_add_tiles" msgid="8933270127508303672">"Ține apăsat și trage pentru a adăuga carduri"</string>
+    <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Ține apăsat și trage pentru a rearanja cardurile"</string>
+    <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Trage aici pentru a elimina"</string>
+    <string name="drag_to_remove_disabled" msgid="933046987838658850">"Ai nevoie de cel puțin <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> carduri"</string>
+    <string name="qs_edit" msgid="5583565172803472437">"Editează"</string>
     <string name="tuner_time" msgid="2450785840990529997">"Oră"</string>
   <string-array name="clock_options">
     <item msgid="3986445361435142273">"Afișează orele, minutele și secundele"</item>
@@ -640,49 +639,47 @@
     <item msgid="3805744470661798712">"Afișează procentajul când se încarcă (prestabilit)"</item>
     <item msgid="8619482474544321778">"Nu afișa această pictogramă"</item>
   </string-array>
-    <string name="tuner_low_priority" msgid="8412666814123009820">"Afișați pictogramele de notificare cu prioritate redusă"</string>
+    <string name="tuner_low_priority" msgid="8412666814123009820">"Afișează pictogramele de notificare cu prioritate redusă"</string>
     <string name="other" msgid="429768510980739978">"Altele"</string>
     <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"eliminați cardul"</string>
     <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adăugați cardul la sfârșit"</string>
-    <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mutați cardul"</string>
-    <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adăugați un card"</string>
-    <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mutați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adăugați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+    <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mută cardul"</string>
+    <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adaugă un card"</string>
+    <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mută pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+    <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string>
     <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editorul pentru setări rapide."</string>
     <string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificare <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
-    <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Deschideți setările."</string>
-    <string name="accessibility_quick_settings_expand" msgid="2609275052412521467">"Deschideți setările rapide."</string>
-    <string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Închideți setările rapide."</string>
+    <string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Deschide setările."</string>
+    <string name="accessibility_quick_settings_expand" msgid="2609275052412521467">"Deschide setările rapide."</string>
+    <string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Închide setările rapide."</string>
     <string name="accessibility_quick_settings_user" msgid="505821942882668619">"Conectat(ă) ca <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_choose_user_action" msgid="4554388498186576087">"alege utilizatorul"</string>
     <string name="data_connection_no_internet" msgid="691058178914184544">"Fără conexiune la internet"</string>
-    <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Deschideți setările <xliff:g id="ID_1">%s</xliff:g>."</string>
-    <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Editați ordinea setărilor."</string>
+    <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Deschide setările <xliff:g id="ID_1">%s</xliff:g>."</string>
+    <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Editează ordinea setărilor."</string>
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meniul de pornire"</string>
     <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> din <xliff:g id="ID_2">%2$d</xliff:g>"</string>
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Ecran de blocare"</string>
     <string name="thermal_shutdown_title" msgid="2702966892682930264">"Telefonul s-a oprit din cauza încălzirii"</string>
-    <string name="thermal_shutdown_message" msgid="6142269839066172984">"Acum telefonul funcționează normal.\nAtingeți pentru mai multe informații"</string>
-    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonul se încălzise prea mult și s-a oprit pentru a se răci. Acum telefonul funcționează normal.\n\nTelefonul s-ar putea încălzi prea mult dacă:\n	• folosiți aplicații care consumă multe resurse (de ex., jocuri, aplicații video/de navigare);\n	• descărcați/încărcați fișiere mari;\n	• folosiți telefonul la temperaturi ridicate."</string>
-    <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vedeți pașii pentru îngrijire"</string>
+    <string name="thermal_shutdown_message" msgid="6142269839066172984">"Acum telefonul funcționează normal.\nAtinge pentru mai multe informații"</string>
+    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonul se încălzise prea mult și s-a oprit pentru a se răci. Acum telefonul funcționează normal.\n\nTelefonul s-ar putea încălzi prea mult dacă:\n	• folosești aplicații care consumă multe resurse (de ex., jocuri, aplicații video/de navigare);\n	• descarci/încarci fișiere mari;\n	• folosești telefonul la temperaturi ridicate."</string>
+    <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vezi pașii pentru îngrijire"</string>
     <string name="high_temp_title" msgid="2218333576838496100">"Telefonul se încălzește"</string>
-    <string name="high_temp_notif_message" msgid="1277346543068257549">"Anumite funcții sunt limitate în timp ce telefonul se răcește.\nAtingeți pentru mai multe informații"</string>
-    <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonul va încerca automat să se răcească. Puteți folosi telefonul în continuare, dar este posibil să funcționeze mai lent.\n\nDupă ce se răcește, telefonul va funcționa normal."</string>
-    <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vedeți pașii pentru îngrijire"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
-    <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vedeți pașii pentru îngrijire"</string>
+    <string name="high_temp_notif_message" msgid="1277346543068257549">"Anumite funcții sunt limitate în timp ce telefonul se răcește.\nAtinge pentru mai multe informații"</string>
+    <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonul va încerca automat să se răcească. Îl poți folosi în continuare, dar e posibil să funcționeze mai lent.\n\nDupă ce se răcește, telefonul va funcționa normal."</string>
+    <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vezi pașii pentru îngrijire"</string>
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Deconectează dispozitivul"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Dispozitivul se încălzește lângă portul de încărcare. Dacă este conectat la un încărcător sau accesoriu USB, deconectează-l și ai grijă, deoarece și cablul poate fi cald."</string>
+    <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Vezi pașii pentru îngrijire"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Comanda rapidă din stânga"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Comanda rapidă din dreapta"</string>
     <string name="lockscreen_unlock_left" msgid="1417801334370269374">"Comanda rapidă din stânga și deblochează"</string>
     <string name="lockscreen_unlock_right" msgid="4658008735541075346">"Comanda rapidă din dreapta și deblochează"</string>
     <string name="lockscreen_none" msgid="4710862479308909198">"Niciuna"</string>
-    <string name="tuner_launch_app" msgid="3906265365971743305">"Lansați <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="tuner_launch_app" msgid="3906265365971743305">"Lansează <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="tuner_other_apps" msgid="7767462881742291204">"Alte aplicații"</string>
     <string name="tuner_circle" msgid="5270591778160525693">"Cerc"</string>
     <string name="tuner_plus" msgid="4130366441154416484">"Plus"</string>
@@ -701,9 +698,9 @@
     <string name="instant_apps" msgid="8337185853050247304">"Aplicații instantanee"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> rulează"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplicația a fost deschisă fără a fi instalată."</string>
-    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Aplicația a fost deschisă fără a fi instalată. Atingeți pentru a afla mai multe."</string>
+    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Aplicația a fost deschisă fără a fi instalată. Atinge pentru a afla mai multe."</string>
     <string name="app_info" msgid="5153758994129963243">"Informații aplicație"</string>
-    <string name="go_to_web" msgid="636673528981366511">"Accesați browserul"</string>
+    <string name="go_to_web" msgid="636673528981366511">"Accesează browserul"</string>
     <string name="mobile_data" msgid="4564407557775397216">"Date mobile"</string>
     <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
@@ -714,21 +711,21 @@
     <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"Funcția Nu deranja a fost activată de o aplicație (<xliff:g id="ID_1">%s</xliff:g>)."</string>
     <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"Funcția Nu deranja a fost activată de o regulă automată sau de o aplicație."</string>
     <string name="running_foreground_services_title" msgid="5137313173431186685">"Aplicațiile rulează în fundal"</string>
-    <string name="running_foreground_services_msg" msgid="3009459259222695385">"Atingeți pentru mai multe detalii privind bateria și utilizarea datelor"</string>
-    <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivați datele mobile?"</string>
-    <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu veți avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string>
-    <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul dvs."</string>
-    <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu vă pot verifica răspunsul."</string>
-    <string name="slice_permission_title" msgid="3262615140094151017">"Permiteți <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
+    <string name="running_foreground_services_msg" msgid="3009459259222695385">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string>
+    <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string>
+    <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string>
+    <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string>
+    <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string>
+    <string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="slice_permission_text_2" msgid="6758906940360746983">"- Poate efectua acțiuni în <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="slice_permission_checkbox" msgid="4242888137592298523">"Permiteți <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string>
-    <string name="slice_permission_allow" msgid="6340449521277951123">"Permiteți"</string>
-    <string name="slice_permission_deny" msgid="6870256451658176895">"Refuzați"</string>
-    <string name="auto_saver_title" msgid="6873691178754086596">"Atingeți pentru a programa Economisirea energiei"</string>
-    <string name="auto_saver_text" msgid="3214960308353838764">"Porniți dacă este probabil ca bateria să se descarce"</string>
+    <string name="slice_permission_checkbox" msgid="4242888137592298523">"Permite <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string>
+    <string name="slice_permission_allow" msgid="6340449521277951123">"Permite"</string>
+    <string name="slice_permission_deny" msgid="6870256451658176895">"Refuz"</string>
+    <string name="auto_saver_title" msgid="6873691178754086596">"Atinge pentru a programa Economisirea energiei"</string>
+    <string name="auto_saver_text" msgid="3214960308353838764">"Pornește dacă e probabil ca bateria să se descarce"</string>
     <string name="no_auto_saver_action" msgid="7467924389609773835">"Nu, mulțumesc"</string>
-    <string name="heap_dump_tile_name" msgid="2464189856478823046">"Extrageți memoria SysUI"</string>
+    <string name="heap_dump_tile_name" msgid="2464189856478823046">"Extrage memoria SysUI"</string>
     <string name="ongoing_privacy_dialog_a11y_title" msgid="2205794093673327974">"În uz"</string>
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplicațiile folosesc <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
@@ -775,16 +772,16 @@
     <string name="accessibility_magnifier_edit" msgid="1522877239671820636">"Editează"</string>
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setările ferestrei de mărire"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atingeți pentru a deschide funcțiile de accesibilitate. Personalizați sau înlocuiți butonul în Setări.\n\n"<annotation id="link">"Afișați setările"</annotation></string>
-    <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mutați butonul spre margine pentru a-l ascunde temporar"</string>
-    <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mutați în stânga sus"</string>
-    <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mutați în dreapta sus"</string>
-    <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mutați în stânga jos"</string>
-    <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mutați în dreapta jos"</string>
+    <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string>
+    <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string>
+    <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mută în dreapta sus"</string>
+    <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mută în stânga jos"</string>
+    <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mută în dreapta jos"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mutați în afară și ascundeți"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mutați în afară și afișați"</string>
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Activați / dezactivați"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string>
-    <string name="controls_providers_title" msgid="6879775889857085056">"Alegeți aplicația pentru a adăuga comenzi"</string>
+    <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S-a adăugat # comandă.}few{S-au adăugat # comenzi.}other{S-au adăugat # de comenzi.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Eliminată"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Marcată ca preferată"</string>
@@ -792,18 +789,18 @@
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"S-a anulat marcarea ca preferată"</string>
     <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"marcați ca preferată"</string>
     <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"anulați marcarea ca preferată"</string>
-    <string name="accessibility_control_move" msgid="8980344493796647792">"Mutați pe poziția <xliff:g id="NUMBER">%d</xliff:g>"</string>
+    <string name="accessibility_control_move" msgid="8980344493796647792">"Mută pe poziția <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="controls_favorite_default_title" msgid="967742178688938137">"Comenzi"</string>
-    <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Alegeți comenzile de accesat din Setările rapide"</string>
+    <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Alege comenzile de accesat din Setările rapide"</string>
     <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Țineți apăsat și trageți pentru a rearanja comenzile"</string>
     <string name="controls_favorite_removed" msgid="5276978408529217272">"Au fost șterse toate comenzile"</string>
     <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Modificările nu au fost salvate"</string>
-    <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Vedeți alte aplicații"</string>
+    <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Vezi alte aplicații"</string>
     <string name="controls_favorite_load_error" msgid="5126216176144877419">"Comenzile nu au putut fi încărcate. Accesați aplicația <xliff:g id="APP">%s</xliff:g> pentru a vă asigura că setările aplicației nu s-au schimbat."</string>
     <string name="controls_favorite_load_none" msgid="7687593026725357775">"Nu sunt disponibile comenzi compatibile"</string>
     <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Altul"</string>
-    <string name="controls_dialog_title" msgid="2343565267424406202">"Adăugați la comenzile dispozitivelor"</string>
-    <string name="controls_dialog_ok" msgid="2770230012857881822">"Adăugați"</string>
+    <string name="controls_dialog_title" msgid="2343565267424406202">"Adaugă la comenzile dispozitivelor"</string>
+    <string name="controls_dialog_ok" msgid="2770230012857881822">"Adaugă"</string>
     <string name="controls_dialog_message" msgid="342066938390663844">"Sugerat de <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="controls_tile_locked" msgid="731547768182831938">"Dispozitiv blocat"</string>
     <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Vedeți și controlați dispozitivele de pe ecranul de blocare?"</string>
@@ -815,10 +812,10 @@
     <string name="controls_pin_use_alphanumeric" msgid="8478371861023048414">"Codul PIN conține litere sau simboluri"</string>
     <string name="controls_pin_verify" msgid="3452778292918877662">"Verificați <xliff:g id="DEVICE">%s</xliff:g>"</string>
     <string name="controls_pin_wrong" msgid="6162694056042164211">"Cod PIN greșit"</string>
-    <string name="controls_pin_instructions" msgid="6363309783822475238">"Introduceți codul PIN"</string>
-    <string name="controls_pin_instructions_retry" msgid="1566667581012131046">"Încercați alt cod PIN"</string>
-    <string name="controls_confirmation_message" msgid="7744104992609594859">"Confirmați schimbarea pentru <xliff:g id="DEVICE">%s</xliff:g>"</string>
-    <string name="controls_structure_tooltip" msgid="4355922222944447867">"Glisați pentru a vedea mai multe"</string>
+    <string name="controls_pin_instructions" msgid="6363309783822475238">"Introdu codul PIN"</string>
+    <string name="controls_pin_instructions_retry" msgid="1566667581012131046">"Încearcă alt cod PIN"</string>
+    <string name="controls_confirmation_message" msgid="7744104992609594859">"Confirmă schimbarea pentru <xliff:g id="DEVICE">%s</xliff:g>"</string>
+    <string name="controls_structure_tooltip" msgid="4355922222944447867">"Glisează pentru a vedea mai multe"</string>
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Se încarcă recomandările"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="4780485355795635052">"Ascundeți comanda media pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
@@ -828,36 +825,36 @@
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
-    <string name="controls_media_button_play" msgid="2705068099607410633">"Redați"</string>
-    <string name="controls_media_button_pause" msgid="8614887780950376258">"Întrerupeți"</string>
+    <string name="controls_media_button_play" msgid="2705068099607410633">"Redă"</string>
+    <string name="controls_media_button_pause" msgid="8614887780950376258">"Întrerupe"</string>
     <string name="controls_media_button_prev" msgid="8126822360056482970">"Melodia anterioară"</string>
     <string name="controls_media_button_next" msgid="6662636627525947610">"Melodia următoare"</string>
     <string name="controls_media_button_connecting" msgid="3138354625847598095">"Se conectează"</string>
-    <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string>
-    <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
-    <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
-    <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
-    <string name="media_transfer_undo" msgid="1895606387620728736">"Anulați"</string>
+    <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redă"</string>
+    <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschide <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
+    <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+    <string name="media_transfer_undo" msgid="1895606387620728736">"Anulează"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropiați-vă pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Mergeți mai aproape de <xliff:g id="DEVICENAME">%1$s</xliff:g> ca să redați acolo"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încercați din nou."</string>
+    <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string>
     <string name="controls_error_removed_message" msgid="2885911717034750542">"Nu s-a putut accesa <xliff:g id="DEVICE">%1$s</xliff:g>. Accesați aplicația <xliff:g id="APPLICATION">%2$s</xliff:g> pentru a vă asigura de disponibilitatea comenzii și că setările aplicației nu s-au schimbat."</string>
-    <string name="controls_open_app" msgid="483650971094300141">"Deschideți aplicația"</string>
+    <string name="controls_open_app" msgid="483650971094300141">"Deschide aplicația"</string>
     <string name="controls_error_generic" msgid="352500456918362905">"Starea nu se poate încărca"</string>
     <string name="controls_error_failed" msgid="960228639198558525">"Eroare, încercați din nou"</string>
-    <string name="controls_menu_add" msgid="4447246119229920050">"Adăugați comenzi"</string>
-    <string name="controls_menu_edit" msgid="890623986951347062">"Editați comenzile"</string>
-    <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adăugați ieșiri"</string>
+    <string name="controls_menu_add" msgid="4447246119229920050">"Adaugă comenzi"</string>
+    <string name="controls_menu_edit" msgid="890623986951347062">"Editează comenzile"</string>
+    <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adaugă ieșiri"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string>
     <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S-au selectat <xliff:g id="COUNT">%1$d</xliff:g> dispozitive"</string>
     <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deconectat)"</string>
-    <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Nu se poate comuta. Atingeți pentru a încerca din nou."</string>
-    <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Conectați un dispozitiv"</string>
+    <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Nu se poate comuta. Atinge pentru a încerca din nou."</string>
+    <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Conectează un dispozitiv"</string>
     <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Pentru a proiecta această sesiune, deschideți aplicația."</string>
     <string name="media_output_dialog_unknown_launch_app_name" msgid="1084899329829371336">"Aplicație necunoscută"</string>
     <string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Nu mai proiectați"</string>
@@ -869,14 +866,14 @@
     <string name="media_output_broadcasting_message" msgid="4150299923404886073">"Ca să asculte transmisia dvs., persoanele din apropiere cu dispozitive Bluetooth compatibile vă pot scana codul QR sau pot folosi numele și parola transmisiei."</string>
     <string name="media_output_broadcast_name" msgid="8786127091542624618">"Numele transmisiei"</string>
     <string name="media_output_broadcast_code" msgid="870795639644728542">"Parolă"</string>
-    <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Salvați"</string>
+    <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Salvează"</string>
     <string name="media_output_broadcast_starting" msgid="8130153654166235557">"Începe…"</string>
     <string name="media_output_broadcast_start_failed" msgid="3670835946856129775">"Nu se poate transmite"</string>
-    <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"Nu se poate salva. Încercați din nou."</string>
+    <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"Nu se poate salva. Încearcă din nou."</string>
     <string name="media_output_broadcast_last_update_error" msgid="5484328807296895491">"Nu se poate salva."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numărul versiunii s-a copiat în clipboard."</string>
-    <string name="basic_status" msgid="2315371112182658176">"Deschideți conversația"</string>
+    <string name="basic_status" msgid="2315371112182658176">"Deschide conversația"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgeturi pentru conversație"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Atingeți o conversație ca să o adăugați pe ecranul de pornire"</string>
     <string name="no_conversations_text" msgid="5354115541282395015">"Conversațiile dvs. recente se vor afișa aici"</string>
@@ -905,7 +902,7 @@
     <string name="status_before_loading" msgid="1500477307859631381">"Conținutul va apărea în curând"</string>
     <string name="missed_call" msgid="4228016077700161689">"Apel nepreluat"</string>
     <string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
-    <string name="people_tile_description" msgid="8154966188085545556">"Vedeți mesaje recente, apeluri pierdute și actualizări de stare"</string>
+    <string name="people_tile_description" msgid="8154966188085545556">"Vezi mesaje recente, apeluri pierdute și actualizări de stare"</string>
     <string name="people_tile_title" msgid="6589377493334871272">"Conversație"</string>
     <string name="paused_by_dnd" msgid="7856941866433556428">"Întrerupt de Nu deranja"</string>
     <string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> a trimis un mesaj: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
@@ -913,11 +910,11 @@
     <string name="new_status_content_description" msgid="6046637888641308327">"<xliff:g id="NAME">%1$s</xliff:g> are o nouă stare: <xliff:g id="STATUS">%2$s</xliff:g>"</string>
     <string name="person_available" msgid="2318599327472755472">"Disponibil"</string>
     <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ți pentru mai multe informații"</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_fingerprint_label" msgid="5255731221854153660">"Senzor de amprentă"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Autentificați-vă"</string>
-    <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesați dispozitivul"</string>
+    <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesează dispozitivul"</string>
     <string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Folosiți amprenta ca să deschideți"</string>
     <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Autentificare obligatorie. Atingeți senzorul de amprentă pentru a vă autentifica."</string>
     <string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Apel telefonic în desfășurare"</string>
@@ -930,32 +927,32 @@
     <string name="all_network_unavailable" msgid="4112774339909373349">"Nicio rețea disponibilă"</string>
     <string name="turn_on_wifi" msgid="1308379840799281023">"Wi-Fi"</string>
     <string name="tap_a_network_to_connect" msgid="1565073330852369558">"Atingeți o rețea pentru a vă conecta"</string>
-    <string name="unlock_to_view_networks" msgid="5072880496312015676">"Deblocați pentru a vedea rețelele"</string>
+    <string name="unlock_to_view_networks" msgid="5072880496312015676">"Deblochează pentru a vedea rețelele"</string>
     <string name="wifi_empty_list_wifi_on" msgid="3864376632067585377">"Se caută rețele…"</string>
     <string name="wifi_failed_connect_message" msgid="4161863112079000071">"Nu s-a realizat conexiunea la rețea"</string>
     <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Deocamdată, Wi-Fi nu se poate conecta automat"</string>
     <string name="see_all_networks" msgid="3773666844913168122">"Afișează-le pe toate"</string>
     <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pentru a schimba rețeaua, deconectați ethernet"</string>
     <string name="wifi_scan_notify_message" msgid="3753839537448621794">"Pentru a îmbunătăți experiența cu dispozitivul, aplicațiile și serviciile pot să caute în continuare rețele Wi‑Fi chiar și atunci când conexiunea Wi-Fi este dezactivată. Puteți să schimbați acest aspect din setările pentru căutarea de rețele Wi-Fi. "<annotation id="link">"Schimbați"</annotation></string>
-    <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Dezactivați modul Avion"</string>
+    <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Dezactivează modul Avion"</string>
     <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> vrea să adauge următorul card la Setări rapide"</string>
-    <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Adăugați un card"</string>
+    <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Adaugă un card"</string>
     <string name="qs_tile_request_dialog_not_add" msgid="4168716573114067296">"Nu adăugați un card"</string>
-    <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string>
+    <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alege utilizatorul"</string>
     <string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# aplicație este activă}few{# aplicații sunt active}other{# de aplicații sunt active}}"</string>
     <string name="fgs_dot_content_description" msgid="2865071539464777240">"Informații noi"</string>
     <string name="fgs_manager_dialog_title" msgid="5879184257257718677">"Aplicații active"</string>
     <string name="fgs_manager_dialog_message" msgid="2670045017200730076">"Aceste aplicații sunt active și rulează, chiar dacă nu le folosiți. Astfel, funcțiile lor sunt îmbunătățite, dar autonomia bateriei poate fi afectată."</string>
-    <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Opriți"</string>
+    <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Oprește"</string>
     <string name="fgs_manager_app_item_stop_button_stopped_label" msgid="6950382004441263922">"Oprită"</string>
     <string name="clipboard_edit_text_done" msgid="4551887727694022409">"Gata"</string>
     <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"S-a copiat"</string>
     <string name="clipboard_edit_source" msgid="9156488177277788029">"Din <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
-    <string name="clipboard_dismiss_description" msgid="3335990369850165486">"Închideți textul copiat"</string>
-    <string name="clipboard_edit_text_description" msgid="805254383912962103">"Editați textul copiat"</string>
-    <string name="clipboard_edit_image_description" msgid="8904857948976041306">"Editați imaginea copiată"</string>
-    <string name="clipboard_send_nearby_description" msgid="4629769637846717650">"Trimiteți către un dispozitiv din apropiere"</string>
-    <string name="clipboard_text_hidden" msgid="7926899867471812305">"Atingeți pentru a afișa"</string>
+    <string name="clipboard_dismiss_description" msgid="3335990369850165486">"Închide textul copiat"</string>
+    <string name="clipboard_edit_text_description" msgid="805254383912962103">"Editează textul copiat"</string>
+    <string name="clipboard_edit_image_description" msgid="8904857948976041306">"Editează imaginea copiată"</string>
+    <string name="clipboard_send_nearby_description" msgid="4629769637846717650">"Trimite către un dispozitiv din apropiere"</string>
+    <string name="clipboard_text_hidden" msgid="7926899867471812305">"Atinge pentru a afișa"</string>
     <string name="clipboard_text_copied" msgid="5100836834278976679">"Textul a fost copiat"</string>
     <string name="clipboard_image_copied" msgid="3793365360174328722">"Imaginea a fost copiată"</string>
     <string name="clipboard_content_copied" msgid="144452398567828145">"Conținutul a fost copiat"</string>
@@ -963,8 +960,8 @@
     <string name="clipboard_overlay_window_name" msgid="6450043652167357664">"Clipboard"</string>
     <string name="clipboard_image_preview" msgid="2156475174343538128">"Previzualizarea imaginii"</string>
     <string name="clipboard_edit" msgid="4500155216174011640">"editați"</string>
-    <string name="add" msgid="81036585205287996">"Adăugați"</string>
-    <string name="manage_users" msgid="1823875311934643849">"Gestionați utilizatorii"</string>
+    <string name="add" msgid="81036585205287996">"Adaugă"</string>
+    <string name="manage_users" msgid="1823875311934643849">"Gestionează utilizatorii"</string>
     <string name="drag_split_not_supported" msgid="4326847447699729722">"Notificarea nu acceptă tragerea pe ecranul împărțit."</string>
     <string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"Wi‑Fi indisponibil"</string>
     <string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Modul Prioritate"</string>
@@ -978,7 +975,7 @@
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Opriți difuzarea <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Dacă difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbați rezultatul, difuzarea actuală se va opri"</string>
     <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Difuzați <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
-    <string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Schimbați rezultatul"</string>
+    <string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Schimbă rezultatul"</string>
     <string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Necunoscută"</string>
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE, z LLL"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index e848857..08eee34 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Вкл."</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Откл."</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"узнать больше"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Панель навигации"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Расположение кнопок"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Дополнительный тип кнопки \"Влево\""</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Пока телефон не остынет, некоторые функции могут быть недоступны.\nНажмите, чтобы получить дополнительную информацию"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш телефон остынет автоматически.\n\nОбратите внимание, что до тех пор он может работать медленнее, чем обычно."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Подробнее о действиях при перегреве…"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Отключите устройство"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Устройство нагревается в районе зарядного порта. Если оно подключено к зарядному или USB-устройству, отключите его. Будьте осторожны: кабель тоже мог нагреться."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Подробнее о действиях при перегреве…"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ярлык слева"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Ярлык справа"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index fcff90d..c329c22 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ක්‍රියාත්මකයි"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ක්‍රියාවිරහිතයි"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ලබා ගත නොහැකිය"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"තව දැන ගන්න"</string>
     <string name="nav_bar" msgid="4642708685386136807">"සංචලන තීරුව"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"පිරිසැලසුම"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"අමතර වම් බොත්තම් වර්ගය"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"දුරකථනය සිසිල් වන අතරතුර සමහර විශේෂාංග සීමිත විය හැකිය.\nතව තතු සඳහා තට්ටු කරන්න"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"ඔබගේ දුරකථනය ස්වයංක්‍රියව සිසිල් වීමට උත්සාහ කරනු ඇත. ඔබට තවම ඔබේ දුරකථනය භාවිත කළ හැකිය, නමුත් එය සෙමින් ධාවනය විය හැකිය.\n\nඔබේ දුරකථනය සිසිල් වූ පසු, එය සාමාන්‍ය ලෙස ධාවනය වනු ඇත."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"රැකවරණ පියවර බලන්න"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ඔබේ උපාංගය ගලවන්න"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ඔබේ උපාංගය ආරෝපණ කවුළුව අවට උණුසුම් වෙමින් පවතී. එය චාජරයකට හෝ USB උපාංගයකට සම්බන්ධ කර ඇත්නම්, එය ගලවා, කේබලය උණුසුම් විය හැකි බැවින් ප්‍රවේශම් වන්න."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"රැකවරණ පියවර බලන්න"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"වම් කෙටි මග"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"දකුණු කෙටි මග"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index eefb1de..9f9efc4 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Niektoré funkcie budú obmedzené, dokým neklesne teplota telefónu.\nViac sa dozviete po klepnutí."</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Váš telefón sa automaticky pokúsi schladiť. Môžete ho naďalej používať, ale môže fungovať pomalšie.\n\nPo poklese teploty bude telefón fungovať ako normálne."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobraziť opatrenia"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zariadenie"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Zariadenie sa zahrieva pri nabíjacom porte. Ak je pripojené k nabíjačke alebo príslušenstvu USB, odpojte ho a dajte pozor, lebo môže byť horúci aj kábel."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Zobraziť opatrenia"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Ľavá skratka"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Pravá skratka"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 9140361..397659d 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Vklopljeno"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Izklopljeno"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Ni na voljo"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"za več informacij"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Vrstica za krmarjenje"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Postavitev"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Vrsta dodatnega levega gumba"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Nekatere funkcije bodo med ohlajanjem telefona omejene.\nDotaknite se za več informacij"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefon se bo samodejno poskusil ohladiti. Še naprej ga lahko uporabljate, vendar bo morda deloval počasneje.\n\nKo se telefon ohladi, bo zopet deloval kot običajno."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Oglejte si navodila za ukrepanje"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Odklopite napravo"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Naprava se segreva pri vratih za polnjenje. Če je priključena na polnilnik ali dodatek USB, ga odklopite in bodite tem previdni, saj je tudi kabel lahko topel."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Oglejte si navodila za ukrepanje"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Leva bližnjica"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Desna bližnjica"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 99536cb..f714411 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Aktiv"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Joaktiv"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Nuk ofrohet"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"mëso më shumë"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Shiriti i navigimit"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Struktura"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Lloji i butonit shtesë majtas"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Disa veçori janë të kufizuara kur telefoni është duke u ftohur.\nTrokit për më shumë informacione"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefoni yt do të përpiqet automatikisht që të ftohet. Mund ta përdorësh përsëri telefonin, por ai mund të punojë më ngadalë.\n\nPasi telefoni të jetë ftohur, ai do të punojë si normalisht."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Shiko hapat për kujdesin"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Shkëpute pajisjen"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Pajisja jote po nxehet pranë portës së karikimit. Nëse është lidhur me një karikues ose një aksesor USB, shkëpute dhe trego kujdes pasi kablloja mund të jetë e nxehtë po ashtu."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Shiko hapat për kujdesin"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Shkurtorja majtas"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Shkurtorja djathtas"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 8b9822d..9eabe28 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Укључено"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Искључено"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"сазнајте више"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Трака за навигацију"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Распоред"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Додатни тип левог дугмета"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Неке функције су ограничене док се телефон не охлади.\nДодирните за више информација"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Телефон ће аутоматски покушати да се охлади. И даље ћете моћи да користите телефон, али ће спорије реаговати.\n\nКада се телефон охлади, нормално ће радити."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Погледајте упозорења"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Искључите уређај"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Уређај се загрева у близини порта за пуњење. Ако је повезан са пуњачем или USB опремом, искључите је и будите пажљиви јер и кабл може да буде врућ."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Погледајте упозорења"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Лева пречица"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Десна пречица"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 374d552..c7446fa 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"På"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Av"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Inte tillgängligt"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"läs mer"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Navigeringsfält"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Layout"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Knapptyp för extra vänster"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Vissa funktioner är begränsade medan telefonen svalnar.\nTryck för mer information"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Mobilen försöker svalna automatiskt. Du kan fortfarande använda mobilen, men den kan vara långsammare än vanligt.\n\nMobilen fungerar som vanligt när den har svalnat."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Visa alla skötselråd"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Koppla ur enheten"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Enheten börjar bli varm vid laddningsporten. Om den är ansluten till en laddare eller ett USB-tillbehör kopplar du ur den. Var försiktigt eftersom kabeln också kan vara varm."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Visa alla skötselråd"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Vänster genväg"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Höger genväg"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 79e542f..c02ea6c 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Imewashwa"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Imezimwa"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Hakipatikani"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"pata maelezo zaidi"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Sehemu ya viungo muhimu"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Mpangilio"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Aina ya kitufe cha kushoto cha ziada"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Baadhi ya vipengele havitatumika kwenye simu wakati inapoa.\nGusa ili upate maelezo zaidi"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Simu yako itajaribu kupoa kiotomatiki. Bado unaweza kutumia simu yako, lakini huenda ikafanya kazi polepole. \n\nPindi simu yako itakapopoa, itaendelea kufanya kazi kama kawaida."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Angalia hatua za utunzaji"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Chomoa kifaa chako"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Kifaa chako kinapata joto karibu na mlango wa kuchaji. Ikiwa kimeunganishwa kwenye chaja au kifuasi cha USB, kichomoe na uwe makini kwani kebo inaweza kuwa imepata joto."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Angalia hatua za ulinzi"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Njia ya mkato ya kushoto"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Njia ya mkato ya kulia"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index f4d4824..b24ce12 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -66,8 +66,6 @@
     <dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_notifications_scrim_transition_distance</dimen>
     <dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_notifications_scrim_transition_delay</dimen>
     <dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen>
-    <!-- On split-shade, the QS squish transition should start from half height.  -->
-    <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item>
     <!-- On split-shade, there should be no depth effect, so setting the value to 0.  -->
     <dimen name="lockscreen_shade_depth_controller_transition_distance">0dp</dimen>
     <dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index a587e5a..5dcbeb5 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -86,8 +86,6 @@
     <dimen name="lockscreen_shade_qs_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_qs_transition_delay">@dimen/lockscreen_shade_scrim_transition_distance</dimen>
     <dimen name="lockscreen_shade_qs_squish_transition_distance">@dimen/lockscreen_shade_qs_transition_distance</dimen>
-    <!-- On large screen portrait, the QS squish transition should start from half height.  -->
-    <item name="lockscreen_shade_qs_squish_start_fraction" type="dimen" format="float" >0.5</item>
     <dimen name="lockscreen_shade_depth_controller_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_udfps_keyguard_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 1c795bf..303e435 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"ஆன்"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ஆஃப்"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"இல்லை"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"மேலும் அறிக"</string>
     <string name="nav_bar" msgid="4642708685386136807">"வழிசெலுத்தல் பட்டி"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"தளவமைப்பு"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"கூடுதல் இடப்புற பட்டன் வகை"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"மொபைலின் வெப்ப அளவு குறையும் வரை சில அம்சங்களைப் பயன்படுத்த முடியாது.\nமேலும் தகவலுக்கு தட்டவும்"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"உங்கள் மொபைலின் வெப்ப அளவு தானாகவே குறையும். தொடர்ந்து நீங்கள் மொபைலைப் பயன்படுத்தலாம், ஆனால் அதன் வேகம் குறைவாக இருக்கக்கூடும்.\n\nமொபைலின் வெப்ப அளவு குறைந்தவுடன், அது இயல்பு நிலையில் இயங்கும்."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"சாதன இணைப்பைத் துண்டித்தல்"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"சார்ஜிங் போர்ட்டிற்கு அருகே உங்கள் சாதனம் சூடாகிறது. சார்ஜருடனோ USB உபகரணத்துடனோ சாதனம் இணைக்கப்பட்டிருந்தால் அதன் இணைப்பைத் துண்டிக்கவும். கேபிளும் சூடாக இருக்கக்கூடும் என்பதால் கவனத்துடன் கையாளவும்."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"இடப்புற ஷார்ட்கட்"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"வலப்புற ஷார்ட்கட்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index b278f22..643fe1e 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ఫోన్‌ను చల్లబరిచే క్రమంలో కొన్ని ఫీచర్లు పరిమితం చేయబడ్డాయి.\nమరింత సమాచారం కోసం ట్యాప్ చేయండి"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"మీ ఫోన్ ఆటోమేటిక్‌గా చల్లబడటానికి ప్రయత్నిస్తుంది. మీరు ఇప్పటికీ మీ ఫోన్‌ను ఉపయోగించవచ్చు, కానీ దాని పనితీరు నెమ్మదిగా ఉండవచ్చు.\n\nమీ ఫోన్ చల్లబడిన తర్వాత, అది సాధారణ రీతిలో పని చేస్తుంది."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"మీ పరికరాన్ని అన్‌ప్లగ్ చేయండి"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"ఛార్జింగ్ పోర్ట్ దగ్గర ఉంచినప్పుడు మీ పరికరం వేడెక్కుతోంది. ఇది ఛార్జర్ లేదా USB యాక్సెసరీకి కనెక్ట్ చేసి ఉంటే, దాన్ని అన్‌ప్లగ్ చేసి, కేబుల్ వేడెక్కే అవకాశం కూడా ఉన్నందున జాగ్రత్త వహించండి."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ఎడమవైపు షార్ట్‌కట్"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"కుడివైపు షార్ట్‌కట్"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ec0e365..0c20911 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"เปิด"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"ปิด"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"ไม่พร้อมใช้งาน"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"ดูข้อมูลเพิ่มเติม"</string>
     <string name="nav_bar" msgid="4642708685386136807">"แถบนำทาง"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"การจัดวาง"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"ประเภทปุ่มทางซ้ายเพิ่มเติม"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"ฟีเจอร์บางอย่างจะใช้งานได้จำกัดขณะโทรศัพท์เย็นลง\nแตะเพื่อดูข้อมูลเพิ่มเติม"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"โทรศัพท์จะพยายามลดอุณหภูมิลงโดยอัตโนมัติ คุณยังสามารถใช้โทรศัพท์ได้ แต่โทรศัพท์อาจทำงานช้าลง\n\nโทรศัพท์จะทำงานตามปกติเมื่อเย็นลงแล้ว"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ดูขั้นตอนในการดูแลรักษา"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"ถอดปลั๊กอุปกรณ์"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"บริเวณพอร์ตชาร์จของอุปกรณ์เริ่มจะร้อนแล้ว หากมีที่ชาร์จหรืออุปกรณ์เสริม USB เสียบอยู่ ให้ถอดออกอย่างระมัดระวังเพราะสายเส้นนั้นก็อาจจะร้อนด้วยเช่นกัน"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"ดูขั้นตอนในการดูแลรักษา"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"ทางลัดทางซ้าย"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"ทางลัดทางขวา"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index c0b3588..4653b79 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Limitado ang ilang feature habang nagku-cool down ang telepono.\nMag-tap para sa higit pang impormasyon"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Awtomatikong susubukan ng iyong telepono na mag-cool down. Magagamit mo pa rin ang iyong telepono, ngunit maaaring mas mabagal ang paggana nito.\n\nKapag nakapag-cool down na ang iyong telepono, gagana na ito nang normal."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Tingnan ang mga hakbang sa pangangalaga"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Bunutin sa saksakan ang device"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Umiinit ang iyong device malapit sa charging port. Kung nakakonekta ito sa charger o USB accessory, bunutin ito sa saksakan, at mag-ingat dahil posibleng mainit din ang cable."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Tingnan ang mga hakbang sa pangangalaga"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Kaliwang shortcut"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Kanang shortcut"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 389178a..2629f10 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Açık"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Kapalı"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Kullanılamıyor"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"daha fazla bilgi"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Gezinme çubuğu"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Düzen"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Ekstra sol düğme türü"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Telefon soğurken bazı özellikler sınırlı olarak kullanılabilir.\nDaha fazla bilgi için dokunun"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Telefonunuz otomatik olarak soğumaya çalışacak. Bu sırada telefonunuzu kullanmaya devam edebilirsiniz ancak uygulamalar daha yavaş çalışabilir.\n\nTelefonunuz soğuduktan sonra normal şekilde çalışacaktır."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bakımla ilgili adımlara bakın"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızın fişini çekin"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Cihazınız, şarj yuvasının yakınındayken ısınıyor. Şarj cihazına veya USB aksesuarına bağlıysa cihazı çıkarın. Ayrıca, kablo sıcak olabileceği için dikkatli olun."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Bakımla ilgili adımlara bakın"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Sol kısayol"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Sağ kısayol"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 77966e8..aea9d4e 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Увімкнено"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Вимкнено"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Недоступно"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"дізнатися більше"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Панель навігації"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Макет"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Додатковий тип кнопки ліворуч"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Під час охолодження деякі функції обмежуються.\nНатисніть, щоб дізнатися більше"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ваш телефон охолоджуватиметься автоматично. Ви можете далі користуватися телефоном, але він може працювати повільніше.\n\nКоли телефон охолоне, він працюватиме належним чином."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Переглянути запобіжні заходи"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Від’єднайте пристрій"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Пристрій нагрівається біля зарядного порту. Якщо він під’єднаний до зарядного пристрою або USB-аксесуара, від’єднайте його, однак будьте обережні, оскільки кабель також може бути гарячий."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Переглянути застереження"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Комбінація клавіш ліворуч"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Комбінація клавіш праворуч"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 4ebbc70..e3d2d531 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"آن"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"آف"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"غیر دستیاب ہے"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"مزید جانیں"</string>
     <string name="nav_bar" msgid="4642708685386136807">"نیویگیشن بار"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"لے آؤٹ"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"بائيں جانب کی اضافی بٹن کی قسم"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"فون کے ٹھنڈے ہو جانے تک کچھ خصوصیات محدود ہیں۔\nمزید معلومات کیلئے تھپتھپائیں"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"آپ کا فون خودکار طور پر ٹھنڈا ہونے کی کوشش کرے گا۔ آپ ابھی بھی اپنا فون استعمال کر سکتے ہیں، مگر ہو سکتا ہے یہ سست چلے۔\n\nایک بار آپ کا فون ٹھنڈا ہوجائے تو یہ معمول کے مطابق چلے گا۔"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"نگہداشت کے اقدامات ملاحظہ کریں"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"اپنے آلہ کو ان پلگ کریں"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"‏آپ کا آلہ چارجنگ پورٹ کے قریب گرم ہو رہا ہے۔ اگر یہ چارجر یا USB لوازمات سے منسلک ہے تو اسے ان پلگ کریں اور خیال رکھیں کہ کیبل بھی گرم ہو سکتی ہے۔"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"نگہداشت کے اقدامات ملاحظہ کریں"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"بائيں جانب کا شارٹ کٹ"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"دائیں جانب کا شارٹ کٹ"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index c0e7b2f..68be635 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"Đang bật"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Đang tắt"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Không có sẵn"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"tìm hiểu thêm"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Thanh điều hướng"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"Bố cục"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"Loại nút bổ sung bên trái"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Một số tính năng bị hạn chế trong khi điện thoại nguội dần.\nHãy nhấn để biết thêm thông tin"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Điện thoại của bạn sẽ tự động nguội dần. Bạn vẫn có thể sử dụng điện thoại, nhưng điện thoại có thể chạy chậm hơn. \n\nSau khi đã nguội, điện thoại sẽ chạy bình thường."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Xem các bước chăm sóc"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Rút thiết bị ra"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Phần gần cổng sạc của thiết bị đang nóng lên. Nếu thiết bị kết nối với bộ sạc hoặc phụ kiện USB, hãy rút ra một cách thận trọng vì cáp có thể cũng đang nóng."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Xem các bước chăm sóc"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Lối tắt bên trái"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Lối tắt bên phải"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index df0b0e0..04df91b 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"开启"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"关闭"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"不可用"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"了解详情"</string>
     <string name="nav_bar" msgid="4642708685386136807">"导航栏"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"布局"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按钮类型"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"手机降温时,部分功能的使用会受限制。\n点按即可了解详情"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"您的手机将自动尝试降温。您依然可以使用您的手机,但是手机运行速度可能会更慢。\n\n手机降温后,就会恢复正常的运行速度。"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看处理步骤"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔出设备"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"设备的充电接口附近在发热。如果该设备已连接到充电器或 USB 配件,请立即拔掉,并注意充电线也可能会发热。"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看处理步骤"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左快捷方式"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右快捷方式"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
index b476255..6ce948d 100644
--- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
@@ -89,7 +89,7 @@
   <string-array name="tile_states_color_correction">
     <item msgid="2840507878437297682">"不可用"</item>
     <item msgid="1909756493418256167">"关闭"</item>
-    <item msgid="4531508423703413340">"开启"</item>
+    <item msgid="4531508423703413340">"已开启"</item>
   </string-array>
   <string-array name="tile_states_inversion">
     <item msgid="3638187931191394628">"不可用"</item>
@@ -174,6 +174,6 @@
   <string-array name="tile_states_dream">
     <item msgid="6184819793571079513">"不可用"</item>
     <item msgid="8014986104355098744">"关闭"</item>
-    <item msgid="5966994759929723339">"开启"</item>
+    <item msgid="5966994759929723339">"已开启"</item>
   </string-array>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 67f94b7..aedaec6 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -161,7 +161,7 @@
     <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識面孔,請改用指紋完成驗證。"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
+    <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string>
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"開啟"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"關閉"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"無法使用"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"瞭解詳情"</string>
     <string name="nav_bar" msgid="4642708685386136807">"導覽列"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"配置"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按鈕類型"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"手機降溫時,部分功能會受限制。\n輕按即可瞭解詳情"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"手機會自動嘗試降溫。您仍可以使用手機,但手機的運作速度可能較慢。\n\n手機降溫後便會恢復正常。"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看保養步驟"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"充電埠附近的裝置溫度正在上升。如裝置正連接充電器或 USB 配件,請拔除裝置並小心安全,因為電線的溫度可能也偏高。"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看保養步驟"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左捷徑"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右捷徑"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 23189d0..8151cc4 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -598,8 +598,7 @@
     <string name="switch_bar_on" msgid="1770868129120096114">"開啟"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"關閉"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"無法使用"</string>
-    <!-- no translation found for accessibility_tile_disabled_by_policy_action_description (6958422730461646926) -->
-    <skip />
+    <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"瞭解詳情"</string>
     <string name="nav_bar" msgid="4642708685386136807">"導覽列"</string>
     <string name="nav_bar_layout" msgid="4716392484772899544">"配置"</string>
     <string name="left_nav_bar_button_type" msgid="2634852842345192790">"其他向左按鈕類型"</string>
@@ -672,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"手機降溫時,某些功能會受限。\n輕觸即可瞭解詳情"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"手機會自動嘗試降溫。你仍可繼續使用手機,但是手機的運作速度可能會較慢。\n\n手機降溫完畢後,就會恢復正常的運作速度。"</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看處理步驟"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"裝置的充電埠附近越來越熱。如果裝置已連接充電器或 USB 配件,請立即拔除。此外,電線也可能會變熱,請特別留意。"</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"查看處理步驟"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"向左快速鍵"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"向右快速鍵"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 08cd7fc..b2937f8 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -671,10 +671,8 @@
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Ezinye izici zikhawulelwe ngenkathi ifoni iphola.\nThepha mayelana nolwazi olwengeziwe"</string>
     <string name="high_temp_dialog_message" msgid="3793606072661253968">"Ifoni yakho izozama ngokuzenzakalela ukuphola. Ungasasebenzisa ifoni yakho, kodwa ingasebenza ngokungasheshi.\n\nUma ifoni yakho isipholile, izosebenza ngokuvamile."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bona izinyathelo zokunakekelwa"</string>
-    <!-- no translation found for high_temp_alarm_title (8654754369605452169) -->
-    <skip />
-    <!-- no translation found for high_temp_alarm_notify_message (3917622943609118956) -->
-    <skip />
+    <string name="high_temp_alarm_title" msgid="8654754369605452169">"Khipha idivayisi yakho"</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Idivayisi yakho iqala ukufudumala eduze kwembobo yokushaja. Uma ixhunywe kushaja noma insiza ye-USB, yikhiphe, futhi uqaphele njengoba ikhebuli ingase ifudumale."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"Bona izinyathelo zokunakekelwa"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Isinqamuleli sangakwesokunxele"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Isinqamuleli sangakwesokudla"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6592e14..ede6260 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1187,6 +1187,7 @@
 
     <!-- Output switcher panel related dimensions -->
     <dimen name="media_output_dialog_list_max_height">355dp</dimen>
+    <dimen name="media_output_dialog_list_item_height">76dp</dimen>
     <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
     <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
     <dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
@@ -1234,7 +1235,7 @@
 
     <!-- The fraction at which the QS "squish" transition should start during the lockscreen shade
          expansion. 0 is fully collapsed, 1 is fully expanded. -->
-    <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0</item>
+    <item type="dimen" format="float" name="lockscreen_shade_qs_squish_start_fraction">0.5</item>
 
     <!-- Distance that the full shade transition takes in order for depth of the wallpaper to fully
          change.  -->
@@ -1467,6 +1468,8 @@
     <dimen name="fgs_manager_list_top_spacing">12dp</dimen>
 
     <dimen name="media_projection_app_selector_icon_size">32dp</dimen>
+    <dimen name="media_projection_app_selector_recents_padding">16dp</dimen>
+    <dimen name="media_projection_app_selector_loader_size">32dp</dimen>
 
     <!-- Dream overlay related dimensions -->
     <dimen name="dream_overlay_status_bar_height">60dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8084254..f22e797 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -131,6 +131,9 @@
     <!-- For StatusIconContainer to tag its icon views -->
     <item type="id" name="status_bar_view_state_tag" />
 
+    <!-- Status bar -->
+    <item type="id" name="status_bar_dot" />
+
     <!-- Default display cutout on the physical top of screen -->
     <item type="id" name="display_cutout" />
     <item type="id" name="display_cutout_left" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9324e8f..637ac19 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -246,6 +246,16 @@
     <string name="screenrecord_start_label">Start Recording?</string>
     <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]-->
     <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string>
+    <!-- Dropdown option to record the entire screen [CHAR_LIMIT=30]-->
+    <string name="screenrecord_option_entire_screen">Record entire screen</string>
+    <!-- Dropdown option to record a single app [CHAR_LIMIT=30]-->
+    <string name="screenrecord_option_single_app">Record a single app</string>
+    <!-- Message reminding the user that sensitive information may be captured during a full screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+    <string name="screenrecord_warning_entire_screen">While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+    <!-- Message reminding the user that sensitive information may be captured during a single app screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+    <string name="screenrecord_warning_single_app">While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+    <!-- Button to start a screen recording in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]-->
+    <string name="screenrecord_start_recording">Start recording</string>
     <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]-->
     <string name="screenrecord_audio_label">Record audio</string>
     <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]-->
@@ -958,7 +968,26 @@
     <!-- Media projection permission dialog warning title. [CHAR LIMIT=NONE] -->
     <string name="media_projection_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g>?</string>
 
-    <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
+    <!-- Media projection permission dialog title. [CHAR LIMIT=NONE] -->
+    <string name="media_projection_permission_dialog_title">Allow <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> to share or record?</string>
+
+    <!-- Media projection permission dropdown option for capturing the whole screen. [CHAR LIMIT=30] -->
+    <string name="media_projection_permission_dialog_option_entire_screen">Entire screen</string>
+
+    <!-- Media projection permission dropdown option for capturing single app. [CHAR LIMIT=30] -->
+    <string name="media_projection_permission_dialog_option_single_app">A single app</string>
+
+    <!-- Media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_warning_entire_screen">When you\'re sharing, recording, or casting, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_warning_single_app">When you\'re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
+    <string name="media_projection_permission_dialog_continue">Continue</string>
+
+    <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
+    <string name="media_projection_permission_app_selector_title">Share or record an app</string>
 
     <!-- The text to clear all notifications. [CHAR LIMIT=60] -->
     <string name="clear_all_notifications_text">Clear all</string>
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
index cdedc64..9766514 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -93,8 +93,8 @@
             Futures.addCallback(
                 captureToBitmap(window),
                 object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap) {
-                        continuation.resumeWith(Result.success(result))
+                    override fun onSuccess(result: Bitmap?) {
+                        continuation.resumeWith(Result.success(result!!))
                     }
 
                     override fun onFailure(t: Throwable) {
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index f9d66ee..96a5840 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -25,4 +25,39 @@
         <attr name="lockScreenWeight" format="integer" />
         <attr name="chargeAnimationDelay" format="integer" />
     </declare-styleable>
+
+    <declare-styleable name="DoubleShadowAttrDeclare">
+        <attr name="keyShadowBlur" format="dimension" />
+        <attr name="keyShadowOffsetX" format="dimension" />
+        <attr name="keyShadowOffsetY" format="dimension" />
+        <attr name="keyShadowAlpha" format="float" />
+        <attr name="ambientShadowBlur" format="dimension" />
+        <attr name="ambientShadowOffsetX" format="dimension" />
+        <attr name="ambientShadowOffsetY" format="dimension" />
+        <attr name="ambientShadowAlpha" format="float" />
+    </declare-styleable>
+
+    <declare-styleable name="DoubleShadowTextClock">
+        <attr name="keyShadowBlur" />
+        <attr name="keyShadowOffsetX" />
+        <attr name="keyShadowOffsetY" />
+        <attr name="keyShadowAlpha" />
+        <attr name="ambientShadowBlur" />
+        <attr name="ambientShadowOffsetX" />
+        <attr name="ambientShadowOffsetY" />
+        <attr name="ambientShadowAlpha" />
+    </declare-styleable>
+
+    <declare-styleable name="DoubleShadowTextView">
+        <attr name="keyShadowBlur" />
+        <attr name="keyShadowOffsetX" />
+        <attr name="keyShadowOffsetY" />
+        <attr name="keyShadowAlpha" />
+        <attr name="ambientShadowBlur" />
+        <attr name="ambientShadowOffsetX" />
+        <attr name="ambientShadowOffsetY" />
+        <attr name="ambientShadowAlpha" />
+        <attr name="drawableIconSize" format="dimension" />
+        <attr name="drawableIconInsetSize" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 835d6e9..38a3124 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -105,7 +105,8 @@
             )
         }
 
-        pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+        pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
+            true /* allowMultiple */)
         context.contentResolver.registerContentObserver(
             Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
             false,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 733470e..cbd0875 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -218,12 +218,16 @@
                 unregisterSamplingListener();
                 mSamplingListenerRegistered = true;
                 SurfaceControl wrappedStopLayer = wrap(stopLayerControl);
+
+                // pass this to background thread to avoid empty Rect race condition
+                final Rect boundsCopy = new Rect(mSamplingRequestBounds);
+
                 mBackgroundExecutor.execute(() -> {
                     if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
                         return;
                     }
                     mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
-                            wrappedStopLayer, mSamplingRequestBounds);
+                            wrappedStopLayer, boundsCopy);
                 });
                 mRegisteredSamplingBounds.set(mSamplingRequestBounds);
                 mRegisteredStopLayer = stopLayerControl;
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 e77c650..2b2b05ce 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
@@ -81,11 +81,6 @@
      */
     void stopScreenPinning() = 17;
 
-    /*
-     * Notifies that the swipe-to-home (recents animation) is finished.
-     */
-    void notifySwipeToHomeFinished() = 23;
-
     /**
      * Notifies that quickstep will switch to a new task
      * @param rotation indicates which Surface.Rotation the gesture was started in
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 4222744..2111df5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -237,6 +237,13 @@
     public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
             new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
 
+    /**
+     * Indicates that this task for the desktop tile in recents.
+     *
+     * Used when desktop mode feature is enabled.
+     */
+    public boolean desktopTile;
+
     public Task() {
         // Do nothing
     }
@@ -267,6 +274,7 @@
         this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
                 other.isLocked, other.taskDescription, other.topActivity);
         lastSnapshotData.set(other.lastSnapshotData);
+        desktopTile = other.desktopTile;
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
new file mode 100644
index 0000000..72f8b7b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -0,0 +1,209 @@
+package com.android.systemui.shared.recents.utilities;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Utility class to position the thumbnail in the TaskView
+ */
+public class PreviewPositionHelper {
+
+    public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+
+    // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
+    private final RectF mClippedInsets = new RectF();
+    private final Matrix mMatrix = new Matrix();
+    private boolean mIsOrientationChanged;
+
+    public Matrix getMatrix() {
+        return mMatrix;
+    }
+
+    public void setOrientationChanged(boolean orientationChanged) {
+        mIsOrientationChanged = orientationChanged;
+    }
+
+    public boolean isOrientationChanged() {
+        return mIsOrientationChanged;
+    }
+
+    /**
+     * Updates the matrix based on the provided parameters
+     */
+    public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
+            int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+            int currentRotation, boolean isRtl) {
+        boolean isRotated = false;
+        boolean isOrientationDifferent;
+
+        int thumbnailRotation = thumbnailData.rotation;
+        int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+        RectF thumbnailClipHint = new RectF();
+        float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+        float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+        thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+
+        float scale = thumbnailData.scale;
+        final float thumbnailScale;
+
+        // Landscape vs portrait change.
+        // Note: Disable rotation in grid layout.
+        boolean windowingModeSupportsRotation =
+                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+        isOrientationDifferent = isOrientationChange(deltaRotate)
+                && windowingModeSupportsRotation;
+        if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
+            // If we haven't measured , skip the thumbnail drawing and only draw the background
+            // color
+            thumbnailScale = 0f;
+        } else {
+            // Rotate the screenshot if not in multi-window mode
+            isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+
+            float surfaceWidth = thumbnailBounds.width() / scale;
+            float surfaceHeight = thumbnailBounds.height() / scale;
+            float availableWidth = surfaceWidth
+                    - (thumbnailClipHint.left + thumbnailClipHint.right);
+            float availableHeight = surfaceHeight
+                    - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+            float canvasAspect = canvasWidth / (float) canvasHeight;
+            float availableAspect = isRotated
+                    ? availableHeight / availableWidth
+                    : availableWidth / availableHeight;
+            boolean isAspectLargelyDifferent =
+                    Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+                            availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
+            if (isRotated && isAspectLargelyDifferent) {
+                // Do not rotate thumbnail if it would not improve fit
+                isRotated = false;
+                isOrientationDifferent = false;
+            }
+
+            if (isAspectLargelyDifferent) {
+                // Crop letterbox insets if insets isn't already clipped
+                thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
+                thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+                thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+                thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
+                availableWidth = surfaceWidth
+                        - (thumbnailClipHint.left + thumbnailClipHint.right);
+                availableHeight = surfaceHeight
+                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+            }
+
+            final float targetW, targetH;
+            if (isOrientationDifferent) {
+                targetW = canvasHeight;
+                targetH = canvasWidth;
+            } else {
+                targetW = canvasWidth;
+                targetH = canvasHeight;
+            }
+            float targetAspect = targetW / targetH;
+
+            // Update the clipHint such that
+            //   > the final clipped position has same aspect ratio as requested by canvas
+            //   > first fit the width and crop the extra height
+            //   > if that will leave empty space, fit the height and crop the width instead
+            float croppedWidth = availableWidth;
+            float croppedHeight = croppedWidth / targetAspect;
+            if (croppedHeight > availableHeight) {
+                croppedHeight = availableHeight;
+                if (croppedHeight < targetH) {
+                    croppedHeight = Math.min(targetH, surfaceHeight);
+                }
+                croppedWidth = croppedHeight * targetAspect;
+
+                // One last check in case the task aspect radio messed up something
+                if (croppedWidth > surfaceWidth) {
+                    croppedWidth = surfaceWidth;
+                    croppedHeight = croppedWidth / targetAspect;
+                }
+            }
+
+            // Update the clip hints. Align to 0,0, crop the remaining.
+            if (isRtl) {
+                thumbnailClipHint.left += availableWidth - croppedWidth;
+                if (thumbnailClipHint.right < 0) {
+                    thumbnailClipHint.left += thumbnailClipHint.right;
+                    thumbnailClipHint.right = 0;
+                }
+            } else {
+                thumbnailClipHint.right += availableWidth - croppedWidth;
+                if (thumbnailClipHint.left < 0) {
+                    thumbnailClipHint.right += thumbnailClipHint.left;
+                    thumbnailClipHint.left = 0;
+                }
+            }
+            thumbnailClipHint.bottom += availableHeight - croppedHeight;
+            if (thumbnailClipHint.top < 0) {
+                thumbnailClipHint.bottom += thumbnailClipHint.top;
+                thumbnailClipHint.top = 0;
+            } else if (thumbnailClipHint.bottom < 0) {
+                thumbnailClipHint.top += thumbnailClipHint.bottom;
+                thumbnailClipHint.bottom = 0;
+            }
+
+            thumbnailScale = targetW / (croppedWidth * scale);
+        }
+
+        if (!isRotated) {
+            mMatrix.setTranslate(
+                    -thumbnailClipHint.left * scale,
+                    -thumbnailClipHint.top * scale);
+        } else {
+            setThumbnailRotation(deltaRotate, thumbnailBounds);
+        }
+
+        mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
+
+        mMatrix.postScale(thumbnailScale, thumbnailScale);
+        mIsOrientationChanged = isOrientationDifferent;
+    }
+
+    private int getRotationDelta(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
+    /**
+     * @param deltaRotation the number of 90 degree turns from the current orientation
+     * @return {@code true} if the change in rotation results in a shift from landscape to
+     * portrait or vice versa, {@code false} otherwise
+     */
+    private boolean isOrientationChange(int deltaRotation) {
+        return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+    }
+
+    private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
+        float translateX = 0;
+        float translateY = 0;
+
+        mMatrix.setRotate(90 * deltaRotate);
+        switch (deltaRotate) { /* Counter-clockwise */
+            case Surface.ROTATION_90:
+                translateX = thumbnailPosition.height();
+                break;
+            case Surface.ROTATION_270:
+                translateY = thumbnailPosition.width();
+                break;
+            case Surface.ROTATION_180:
+                translateX = thumbnailPosition.width();
+                translateY = thumbnailPosition.height();
+                break;
+        }
+        mMatrix.postTranslate(translateX, translateY);
+    }
+
+    public RectF getClippedInsets() {
+        return mClippedInsets;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 56326e3..77a13bd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -62,6 +62,16 @@
         return false; // Default
     }
 
+    /**
+     * Compares the ratio of two quantities and returns whether that ratio is greater than the
+     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+     * of a percentage.
+     */
+    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+            float bound) {
+        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+    }
+
     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
     public static float computeContrastBetweenColors(int bg, int fg) {
         float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
new file mode 100644
index 0000000..3748eba
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.shadow
+
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.RenderEffect
+import android.graphics.RenderNode
+import android.graphics.Shader
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.InsetDrawable
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+
+/** A component to draw an icon with two layers of shadows. */
+class DoubleShadowIconDrawable(
+    keyShadowInfo: ShadowInfo,
+    ambientShadowInfo: ShadowInfo,
+    iconDrawable: Drawable,
+    iconSize: Int,
+    val iconInsetSize: Int
+) : Drawable() {
+    private val mAmbientShadowInfo: ShadowInfo
+    private val mCanvasSize: Int
+    private val mKeyShadowInfo: ShadowInfo
+    private val mIconDrawable: InsetDrawable
+    private val mDoubleShadowNode: RenderNode
+
+    init {
+        mCanvasSize = iconSize + iconInsetSize * 2
+        mKeyShadowInfo = keyShadowInfo
+        mAmbientShadowInfo = ambientShadowInfo
+        setBounds(0, 0, mCanvasSize, mCanvasSize)
+        mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize)
+        mIconDrawable.setBounds(0, 0, mCanvasSize, mCanvasSize)
+        mDoubleShadowNode = createShadowRenderNode()
+    }
+
+    private fun createShadowRenderNode(): RenderNode {
+        val renderNode = RenderNode("DoubleShadowNode")
+        renderNode.setPosition(0, 0, mCanvasSize, mCanvasSize)
+        // Create render effects
+        val ambientShadow =
+            createShadowRenderEffect(
+                mAmbientShadowInfo.blur,
+                mAmbientShadowInfo.offsetX,
+                mAmbientShadowInfo.offsetY,
+                mAmbientShadowInfo.alpha
+            )
+        val keyShadow =
+            createShadowRenderEffect(
+                mKeyShadowInfo.blur,
+                mKeyShadowInfo.offsetX,
+                mKeyShadowInfo.offsetY,
+                mKeyShadowInfo.alpha
+            )
+        val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+        renderNode.setRenderEffect(blend)
+        return renderNode
+    }
+
+    private fun createShadowRenderEffect(
+        radius: Float,
+        offsetX: Float,
+        offsetY: Float,
+        alpha: Float
+    ): RenderEffect {
+        return RenderEffect.createColorFilterEffect(
+            PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY),
+            RenderEffect.createOffsetEffect(
+                offsetX,
+                offsetY,
+                RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
+            )
+        )
+    }
+
+    override fun draw(canvas: Canvas) {
+        if (canvas.isHardwareAccelerated) {
+            if (!mDoubleShadowNode.hasDisplayList()) {
+                // Record render node if its display list is not recorded or discarded
+                // (which happens when it's no longer drawn by anything).
+                val recordingCanvas = mDoubleShadowNode.beginRecording()
+                mIconDrawable.draw(recordingCanvas)
+                mDoubleShadowNode.endRecording()
+            }
+            canvas.drawRenderNode(mDoubleShadowNode)
+        }
+        mIconDrawable.draw(canvas)
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.TRANSPARENT
+    }
+
+    override fun setAlpha(alpha: Int) {
+        mIconDrawable.alpha = alpha
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        mIconDrawable.colorFilter = colorFilter
+    }
+
+    override fun setTint(color: Int) {
+        mIconDrawable.setTint(color)
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
new file mode 100644
index 0000000..f2db129
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.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.systemui.shared.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.widget.TextClock
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */
+class DoubleShadowTextClock
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextClock(context, attrs, defStyleAttr, defStyleRes) {
+    private val mAmbientShadowInfo: ShadowInfo
+    private val mKeyShadowInfo: ShadowInfo
+
+    init {
+        val attributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.DoubleShadowTextClock,
+                defStyleAttr,
+                defStyleRes
+            )
+        try {
+            val keyShadowBlur =
+                attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0)
+            val keyShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_keyShadowOffsetX,
+                    0
+                )
+            val keyShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_keyShadowOffsetY,
+                    0
+                )
+            val keyShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextClock_keyShadowAlpha, 0f)
+            mKeyShadowInfo =
+                ShadowInfo(
+                    keyShadowBlur.toFloat(),
+                    keyShadowOffsetX.toFloat(),
+                    keyShadowOffsetY.toFloat(),
+                    keyShadowAlpha
+                )
+            val ambientShadowBlur =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowBlur,
+                    0
+                )
+            val ambientShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowOffsetX,
+                    0
+                )
+            val ambientShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowOffsetY,
+                    0
+                )
+            val ambientShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextClock_ambientShadowAlpha, 0f)
+            mAmbientShadowInfo =
+                ShadowInfo(
+                    ambientShadowBlur.toFloat(),
+                    ambientShadowOffsetX.toFloat(),
+                    ambientShadowOffsetY.toFloat(),
+                    ambientShadowAlpha
+                )
+        } finally {
+            attributes.recycle()
+        }
+    }
+
+    public override fun onDraw(canvas: Canvas) {
+        applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
index b1dc5a2..eaac93d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
@@ -14,31 +14,33 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.complication
+package com.android.systemui.shared.shadow
 
 import android.graphics.Canvas
+import android.graphics.Color
 import android.widget.TextView
-import androidx.annotation.ColorInt
 
-class DoubleShadowTextHelper
-constructor(
-    private val keyShadowInfo: ShadowInfo,
-    private val ambientShadowInfo: ShadowInfo,
-) {
+object DoubleShadowTextHelper {
     data class ShadowInfo(
         val blur: Float,
         val offsetX: Float = 0f,
         val offsetY: Float = 0f,
-        @ColorInt val color: Int
+        val alpha: Float
     )
 
-    fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) {
+    fun applyShadows(
+        keyShadowInfo: ShadowInfo,
+        ambientShadowInfo: ShadowInfo,
+        view: TextView,
+        canvas: Canvas,
+        onDrawCallback: () -> Unit
+    ) {
         // We enhance the shadow by drawing the shadow twice
         view.paint.setShadowLayer(
             ambientShadowInfo.blur,
             ambientShadowInfo.offsetX,
             ambientShadowInfo.offsetY,
-            ambientShadowInfo.color
+            Color.argb(ambientShadowInfo.alpha, 0f, 0f, 0f)
         )
         onDrawCallback()
         canvas.save()
@@ -53,7 +55,7 @@
             keyShadowInfo.blur,
             keyShadowInfo.offsetX,
             keyShadowInfo.offsetY,
-            keyShadowInfo.color
+            Color.argb(keyShadowInfo.alpha, 0f, 0f, 0f)
         )
         onDrawCallback()
         canvas.restore()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
new file mode 100644
index 0000000..25d2721
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */
+class DoubleShadowTextView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+    private val mKeyShadowInfo: ShadowInfo
+    private val mAmbientShadowInfo: ShadowInfo
+
+    init {
+        val attributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.DoubleShadowTextView,
+                defStyleAttr,
+                defStyleRes
+            )
+        val drawableSize: Int
+        val drawableInsetSize: Int
+        try {
+            val keyShadowBlur =
+                attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0)
+            val keyShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_keyShadowOffsetX,
+                    0
+                )
+            val keyShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_keyShadowOffsetY,
+                    0
+                )
+            val keyShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f)
+            mKeyShadowInfo =
+                ShadowInfo(
+                    keyShadowBlur.toFloat(),
+                    keyShadowOffsetX.toFloat(),
+                    keyShadowOffsetY.toFloat(),
+                    keyShadowAlpha
+                )
+            val ambientShadowBlur =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowBlur,
+                    0
+                )
+            val ambientShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowOffsetX,
+                    0
+                )
+            val ambientShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowOffsetY,
+                    0
+                )
+            val ambientShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f)
+            mAmbientShadowInfo =
+                ShadowInfo(
+                    ambientShadowBlur.toFloat(),
+                    ambientShadowOffsetX.toFloat(),
+                    ambientShadowOffsetY.toFloat(),
+                    ambientShadowAlpha
+                )
+            drawableSize =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_drawableIconSize,
+                    0
+                )
+            drawableInsetSize =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_drawableIconInsetSize,
+                    0
+                )
+        } finally {
+            attributes.recycle()
+        }
+
+        val drawables = arrayOf<Drawable?>(null, null, null, null)
+        for ((index, drawable) in compoundDrawablesRelative.withIndex()) {
+            if (drawable == null) continue
+            drawables[index] =
+                DoubleShadowIconDrawable(
+                    mKeyShadowInfo,
+                    mAmbientShadowInfo,
+                    drawable,
+                    drawableSize,
+                    drawableInsetSize
+                )
+        }
+        setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3])
+    }
+
+    public override fun onDraw(canvas: Canvas) {
+        applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+    }
+}
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 f0210fd..5d6598d 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
@@ -49,6 +49,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
     public static final int CUJ_SPLIT_SCREEN_ENTER =
             InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
+    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
+            InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -57,6 +59,7 @@
             CUJ_APP_CLOSE_TO_PIP,
             CUJ_QUICK_SWITCH,
             CUJ_APP_LAUNCH_FROM_WIDGET,
+            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION
     })
     @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 97e0242..f2742b7 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
@@ -43,23 +43,8 @@
     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";
-    // See IPip.aidl
-    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
-    // See ISplitScreen.aidl
-    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
-    // See IOneHanded.aidl
-    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
-    // See IShellTransitions.aidl
-    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
-            "extra_shell_shell_transitions";
-    // See IStartingWindow.aidl
-    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
-            "extra_shell_starting_window";
     // See ISysuiUnlockAnimationController.aidl
     public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
-    // See IRecentTasks.aidl
-    public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
-    public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
 
     public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index f82e7db..71470e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -52,17 +52,15 @@
     val becauseCannotSkipBouncer: Boolean,
     val biometricSettingEnabledForUser: Boolean,
     val bouncerFullyShown: Boolean,
-    val bouncerIsOrWillShow: Boolean,
     val faceAuthenticated: Boolean,
     val faceDisabled: Boolean,
     val faceLockedOut: Boolean,
     val fpLockedOut: Boolean,
     val goingToSleep: Boolean,
-    val keyguardAwakeExcludingBouncerShowing: Boolean,
+    val keyguardAwake: Boolean,
     val keyguardGoingAway: Boolean,
     val listeningForFaceAssistant: Boolean,
     val occludingAppRequestingFaceAuth: Boolean,
-    val onlyFaceEnrolled: Boolean,
     val primaryUser: Boolean,
     val scanningAllowedByStrongAuth: Boolean,
     val secureCameraLaunched: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f73c98e..632fcd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -22,8 +22,6 @@
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
 
 import static java.lang.Integer.max;
 
@@ -87,8 +85,8 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter;
 import com.android.systemui.user.data.source.UserRecord;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -1075,6 +1073,11 @@
                         android.R.attr.textColorPrimary));
                 header.setBackground(mView.getContext().getDrawable(
                         R.drawable.bouncer_user_switcher_header_bg));
+                Drawable keyDownDrawable =
+                        ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId(
+                                R.id.user_switcher_key_down);
+                keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(),
+                        android.R.attr.textColorPrimary));
             }
         }
 
@@ -1098,6 +1101,7 @@
                 return;
             }
 
+            mView.setAlpha(1f);
             mUserSwitcherViewGroup.setAlpha(0f);
             ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
                     1f);
@@ -1137,7 +1141,7 @@
 
             KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
 
-            BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
+            BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
                 @Override
                 public View getView(int position, View convertView, ViewGroup parent) {
                     UserRecord item = getItem(position);
@@ -1172,8 +1176,7 @@
                     }
                     textView.setSelected(item == currentUser);
                     view.setEnabled(item.isSwitchToEnabled);
-                    view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA :
-                            USER_SWITCH_DISABLED_ALPHA);
+                    UserSwitcherController.setSelectableAlpha(view);
                     return view;
                 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index d8cffd7..5995e85 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -107,6 +107,14 @@
     }
 
     @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        if (mShowDefaultMessage) {
+            showDefaultMessage();
+        }
+    }
+
+    @Override
     void resetState() {
         super.resetState();
         mStateMachine.reset();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7e04f04..3c375f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -69,8 +69,7 @@
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
@@ -113,7 +112,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
@@ -126,6 +124,9 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
@@ -266,9 +267,9 @@
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
-    private final StatusBarStateController mStatusBarStateController;
     private final UiEventLogger mUiEventLogger;
     private final Set<Integer> mFaceAcquiredInfoIgnoreList;
+    private final PackageManager mPackageManager;
     private int mStatusBarState;
     private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
             new StatusBarStateController.StateListener() {
@@ -289,7 +290,7 @@
     };
 
     HashMap<Integer, SimData> mSimDatas = new HashMap<>();
-    HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
+    HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
 
     private int mPhoneState;
     private boolean mKeyguardIsVisible;
@@ -321,34 +322,41 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
-    private ContentObserver mTimeFormatChangeObserver;
+    private final ContentObserver mTimeFormatChangeObserver;
 
     private boolean mSwitchingUser;
 
     private boolean mDeviceInteractive;
-    private SubscriptionManager mSubscriptionManager;
+    private final SubscriptionManager mSubscriptionManager;
     private final TelephonyListenerManager mTelephonyListenerManager;
-    private List<SubscriptionInfo> mSubscriptionInfo;
-    private TrustManager mTrustManager;
-    private UserManager mUserManager;
-    private KeyguardBypassController mKeyguardBypassController;
-    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-    private LockPatternUtils mLockPatternUtils;
-    private final IDreamManager mDreamManager;
-    private boolean mIsDreaming;
+    private final TrustManager mTrustManager;
+    private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
+    private final StatusBarStateController mStatusBarStateController;
+    private final Executor mBackgroundExecutor;
+    private final SensorPrivacyManager mSensorPrivacyManager;
+    private final ActiveUnlockConfig mActiveUnlockConfig;
+    private final PowerManager mPowerManager;
+    private final IDreamManager mDreamManager;
+    private final TelephonyManager mTelephonyManager;
+    @Nullable
+    private final FingerprintManager mFpm;
+    @Nullable
+    private final FaceManager mFaceManager;
+    private final LockPatternUtils mLockPatternUtils;
+    private final boolean mWakeOnFingerprintAcquiredStart;
+
+    private KeyguardBypassController mKeyguardBypassController;
+    private List<SubscriptionInfo> mSubscriptionInfo;
+    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+    private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private boolean mIsFaceEnrolled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private final Executor mBackgroundExecutor;
-    private SensorPrivacyManager mSensorPrivacyManager;
-    private final ActiveUnlockConfig mActiveUnlockConfig;
-    private final PowerManager mPowerManager;
-    private final boolean mWakeOnFingerprintAcquiredStart;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -375,12 +383,10 @@
     }
     private final Handler mHandler;
 
-    private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
-    private BiometricManager mBiometricManager;
-    private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
+    private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
             new IBiometricEnabledOnKeyguardCallback.Stub() {
                 @Override
-                public void onChanged(boolean enabled, int userId) throws RemoteException {
+                public void onChanged(boolean enabled, int userId) {
                     mHandler.post(() -> {
                         mBiometricEnabledForUser.put(userId, enabled);
                         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -399,7 +405,7 @@
         }
     };
 
-    private OnSubscriptionsChangedListener mSubscriptionListener =
+    private final OnSubscriptionsChangedListener mSubscriptionListener =
             new OnSubscriptionsChangedListener() {
                 @Override
                 public void onSubscriptionsChanged() {
@@ -418,11 +424,12 @@
         }
     }
 
-    private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
-    private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
-    private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
-    private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
-    private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
+    private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
+    private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
+    private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
+    private final SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
+    private final SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
+    private final Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<>();
 
     @VisibleForTesting
     SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
@@ -583,7 +590,7 @@
         }
         if (sil == null) {
             // getCompleteActiveSubscriptionInfoList was null callers expect an empty list.
-            mSubscriptionInfo = new ArrayList<SubscriptionInfo>();
+            mSubscriptionInfo = new ArrayList<>();
         } else {
             mSubscriptionInfo = sil;
         }
@@ -718,7 +725,7 @@
      * If the device is dreaming, awakens the device
      */
     public void awakenFromDream() {
-        if (mIsDreaming && mDreamManager != null) {
+        if (mIsDreaming) {
             try {
                 mDreamManager.awaken();
             } catch (RemoteException e) {
@@ -763,12 +770,8 @@
     }
 
     private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
-        mBackgroundExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
-            }
-        });
+        mBackgroundExecutor.execute(
+                () -> mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId));
     }
 
     private void handleFingerprintAuthFailed() {
@@ -851,7 +854,8 @@
         }
     }
 
-    private Runnable mRetryFingerprintAuthentication = new Runnable() {
+    private final Runnable mRetryFingerprintAuthentication = new Runnable() {
+        @SuppressLint("MissingPermission")
         @Override
         public void run() {
             mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
@@ -895,7 +899,7 @@
 
         boolean lockedOutStateChanged = false;
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
-            lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
+            lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             mLogger.d("Fingerprint locked out - requiring strong auth");
             mLockPatternUtils.requireStrongAuth(
@@ -940,9 +944,9 @@
             // that the events will arrive in a particular order. Add a delay here in case
             // an unlock is in progress. In this is a normal unlock the extra delay won't
             // be noticeable.
-            mHandler.postDelayed(() -> {
-                updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-            }, getBiometricLockoutDelay());
+            mHandler.postDelayed(
+                    () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
+                    getBiometricLockoutDelay());
         } else {
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
@@ -1076,7 +1080,7 @@
         }
     }
 
-    private Runnable mRetryFaceAuthentication = new Runnable() {
+    private final Runnable mRetryFaceAuthentication = new Runnable() {
         @Override
         public void run() {
             mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
@@ -1102,12 +1106,8 @@
 
         // Error is always the end of authentication lifecycle
         mFaceCancelSignal = null;
-        boolean cameraPrivacyEnabled = false;
-        if (mSensorPrivacyManager != null) {
-            cameraPrivacyEnabled = mSensorPrivacyManager
-                    .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
-                    SensorPrivacyManager.Sensors.CAMERA);
-        }
+        boolean cameraPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
+                SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA);
 
         if (msgId == FaceManager.FACE_ERROR_CANCELED
                 && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1158,10 +1158,8 @@
         mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
         final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
 
-        mHandler.postDelayed(() -> {
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET);
-        }, getBiometricLockoutDelay());
+        mHandler.postDelayed(() -> updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
 
         if (changed) {
             notifyLockedOutStateChanged(BiometricSourceType.FACE);
@@ -1200,28 +1198,24 @@
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
-    private boolean isTrustDisabled(int userId) {
+    private boolean isTrustDisabled() {
         // Don't allow trust agent if device is secured with a SIM PIN. This is here
         // mainly because there's no other way to prompt the user to enter their SIM PIN
         // once they get past the keyguard screen.
-        final boolean disabledBySimPin = isSimPinSecure();
-        return disabledBySimPin;
+        return isSimPinSecure(); // Disabled by SIM PIN
     }
 
     private boolean isFingerprintDisabled(int userId) {
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
-                & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
+        return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+                        & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
                 || isSimPinSecure();
     }
 
     private boolean isFaceDisabled(int userId) {
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         // TODO(b/140035044)
-        return whitelistIpcs(() -> dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
-                & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
+        return whitelistIpcs(() ->
+                (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+                        & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
                 || isSimPinSecure());
     }
 
@@ -1243,7 +1237,7 @@
     }
 
     public boolean getUserHasTrust(int userId) {
-        return !isTrustDisabled(userId) && mUserHasTrust.get(userId);
+        return !isTrustDisabled() && mUserHasTrust.get(userId);
     }
 
     /**
@@ -1275,7 +1269,7 @@
     }
 
     public boolean getUserTrustIsManaged(int userId) {
-        return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
+        return mUserTrustIsManaged.get(userId) && !isTrustDisabled();
     }
 
     private void updateSecondaryLockscreenRequirement(int userId) {
@@ -1293,7 +1287,7 @@
                 Intent intent =
                         new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
                                 .setPackage(supervisorComponent.getPackageName());
-                ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
+                ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0);
                 if (resolveInfo != null && resolveInfo.serviceInfo != null) {
                     Intent launchIntent =
                             new Intent().setComponent(resolveInfo.serviceInfo.getComponentName());
@@ -1662,22 +1656,19 @@
     CancellationSignal mFingerprintCancelSignal;
     @VisibleForTesting
     CancellationSignal mFaceCancelSignal;
-    private FingerprintManager mFpm;
-    private FaceManager mFaceManager;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
     private boolean mFaceLockedOutPermanent;
-    private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
-    private TelephonyManager mTelephonyManager;
+    private final HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
 
     /**
      * When we receive a
      * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
      * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
      * we need a single object to pass to the handler.  This class helps decode
-     * the intent and provide a {@link SimCard.State} result.
+     * the intent and provide a {@link SimData} result.
      */
     private static class SimData {
         public int simState;
@@ -1898,9 +1889,20 @@
             UiEventLogger uiEventLogger,
             // This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
             Provider<SessionTracker> sessionTrackerProvider,
-            PowerManager powerManager) {
+            PowerManager powerManager,
+            TrustManager trustManager,
+            SubscriptionManager subscriptionManager,
+            UserManager userManager,
+            IDreamManager dreamManager,
+            DevicePolicyManager devicePolicyManager,
+            SensorPrivacyManager sensorPrivacyManager,
+            TelephonyManager telephonyManager,
+            PackageManager packageManager,
+            @Nullable FaceManager faceManager,
+            @Nullable FingerprintManager fingerprintManager,
+            @Nullable BiometricManager biometricManager) {
         mContext = context;
-        mSubscriptionManager = SubscriptionManager.from(context);
+        mSubscriptionManager = subscriptionManager;
         mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
@@ -1914,12 +1916,20 @@
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
         mLogger = logger;
         mUiEventLogger = uiEventLogger;
         mSessionTrackerProvider = sessionTrackerProvider;
         mPowerManager = powerManager;
+        mTrustManager = trustManager;
+        mUserManager = userManager;
+        mDreamManager = dreamManager;
+        mTelephonyManager = telephonyManager;
+        mDevicePolicyManager = devicePolicyManager;
+        mPackageManager = packageManager;
+        mFpm = fingerprintManager;
+        mFaceManager = faceManager;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
         mWakeOnFingerprintAcquiredStart = context.getResources()
                         .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
@@ -2064,8 +2074,7 @@
         // listener now with the service state from the default sub.
         mBackgroundExecutor.execute(() -> {
             int subId = SubscriptionManager.getDefaultSubscriptionId();
-            ServiceState serviceState = mContext.getSystemService(TelephonyManager.class)
-                    .getServiceStateForSubscriber(subId);
+            ServiceState serviceState = mTelephonyManager.getServiceStateForSubscriber(subId);
             mHandler.sendMessage(
                     mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
         });
@@ -2087,26 +2096,21 @@
             e.rethrowAsRuntimeException();
         }
 
-        mTrustManager = context.getSystemService(TrustManager.class);
         mTrustManager.registerTrustListener(this);
 
         setStrongAuthTracker(mStrongAuthTracker);
 
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.getService(DreamService.DREAM_SERVICE));
-
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-            mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+        if (mFpm != null) {
             mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
+            mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+        if (mFaceManager != null) {
             mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
+            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
         }
 
-        if (mFpm != null || mFaceManager != null) {
-            mBiometricManager = context.getSystemService(BiometricManager.class);
-            mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+        if (biometricManager != null) {
+            biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
         }
 
         // in case authenticators aren't registered yet at this point:
@@ -2125,19 +2129,11 @@
             }
         });
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
-        if (mFpm != null) {
-            mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
-        }
-        if (mFaceManager != null) {
-            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
-        }
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
-        mUserManager = context.getSystemService(UserManager.class);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
         int user = ActivityManager.getCurrentUser();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
-        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
         List<UserInfo> allUsers = mUserManager.getUsers();
@@ -2147,22 +2143,8 @@
         }
         updateAirplaneModeState();
 
-        mTelephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (mTelephonyManager != null) {
-            mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
-            // Set initial sim states values.
-            for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
-                int state = mTelephonyManager.getSimState(slot);
-                int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
-                if (subIds != null) {
-                    for (int subId : subIds) {
-                        mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
-                                .sendToTarget();
-                    }
-                }
-            }
-        }
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+        initializeSimState();
 
         mTimeFormatChangeObserver = new ContentObserver(mHandler) {
             @Override
@@ -2179,6 +2161,20 @@
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
     }
 
+    private void initializeSimState() {
+        // Set initial sim states values.
+        for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+            int state = mTelephonyManager.getSimState(slot);
+            int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+            if (subIds != null) {
+                for (int subId : subIds) {
+                    mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+                            .sendToTarget();
+                }
+            }
+        }
+    }
+
     private void updateFaceEnrolled(int userId) {
         mIsFaceEnrolled = whitelistIpcs(
                 () -> mFaceManager != null && mFaceManager.isHardwareDetected()
@@ -2221,7 +2217,7 @@
         }
 
         @Override
-        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+        public void onUserSwitchComplete(int newUserId) {
             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
                     newUserId, 0));
         }
@@ -2579,11 +2575,8 @@
         }
 
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
-        // mKeyguardIsVisible is true even when the bouncer is shown, we don't want to run face auth
-        // on bouncer if both fp and fingerprint are enrolled.
-        final boolean awakeKeyguardExcludingBouncerShowing = mKeyguardIsVisible
-                && mDeviceInteractive && !mGoingToSleep
-                && !statusBarShadeLocked && !mBouncerIsOrWillBeShowing;
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive
+                && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
         final boolean isLockDown =
@@ -2623,16 +2616,15 @@
         final boolean faceDisabledForUser = isFaceDisabled(user);
         final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
         final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
-        final boolean onlyFaceEnrolled = isOnlyFaceEnrolled();
         final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                ((mBouncerFullyShown && !mGoingToSleep && onlyFaceEnrolled)
+                (mBouncerFullyShown
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
-                        || awakeKeyguardExcludingBouncerShowing
+                        || awakeKeyguard
                         || shouldListenForFaceAssistant
                         || mAuthController.isUdfpsFingerDown()
                         || mUdfpsBouncerShowing)
@@ -2641,6 +2633,7 @@
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
                 && !faceAuthenticated
+                && !mGoingToSleep
                 && !fpOrFaceIsLockedOut;
 
         // Aggregate relevant fields for debug logging.
@@ -2653,17 +2646,15 @@
                     becauseCannotSkipBouncer,
                     biometricEnabledForUser,
                     mBouncerFullyShown,
-                    mBouncerIsOrWillBeShowing,
                     faceAuthenticated,
                     faceDisabledForUser,
                     isFaceLockedOut(),
                     fpLockedout,
                     mGoingToSleep,
-                    awakeKeyguardExcludingBouncerShowing,
+                    awakeKeyguard,
                     mKeyguardGoingAway,
                     shouldListenForFaceAssistant,
                     mOccludingAppRequestingFace,
-                    onlyFaceEnrolled,
                     mIsPrimaryUser,
                     strongAuthAllowsScanning,
                     mSecureCameraLaunched,
@@ -2673,11 +2664,6 @@
         return shouldListen;
     }
 
-    private boolean isOnlyFaceEnrolled() {
-        return isFaceEnrolled()
-                && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
-    }
-
     private void maybeLogListenerModelData(KeyguardListenModel model) {
         mLogger.logKeyguardListenerModel(model);
 
@@ -2791,6 +2777,7 @@
         return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
     }
 
+    @SuppressLint("MissingPermission")
     @VisibleForTesting
     boolean isUnlockWithFingerprintPossible(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
@@ -2921,6 +2908,7 @@
         try {
             reply.sendResult(null);
         } catch (RemoteException e) {
+            mLogger.logException(e, "Ignored exception while userSwitching");
         }
     }
 
@@ -3187,7 +3175,7 @@
             return false;
         }
         Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
-        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent,
+        ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
                 0 /* flags */, getCurrentUser());
 
         if (resolveInfo == null) {
@@ -3229,8 +3217,7 @@
                     cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
                 }
             }
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
 
         if (wasBouncerFullyShown != mBouncerFullyShown) {
@@ -3318,11 +3305,7 @@
         }
 
         // change in battery overheat
-        if (current.health != old.health) {
-            return true;
-        }
-
-        return false;
+        return current.health != old.health;
     }
 
     /**
@@ -3373,10 +3356,8 @@
     public void setSwitchingUser(boolean switching) {
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post if first
-        mHandler.post(() -> {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_USER_SWITCHING);
-        });
+        mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_USER_SWITCHING));
     }
 
     private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -3485,7 +3466,7 @@
     /**
      * If any SIM cards are currently secure.
      *
-     * @see #isSimPinSecure(State)
+     * @see #isSimPinSecure(int)
      */
     public boolean isSimPinSecure() {
         // True if any SIM is pin secure
@@ -3532,10 +3513,7 @@
      * @return true if and only if the state has changed for the specified {@code slotId}
      */
     private boolean refreshSimState(int subId, int slotId) {
-        final TelephonyManager tele =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        int state = (tele != null) ?
-                tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
+        int state = mTelephonyManager.getSimState(slotId);
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
@@ -3678,13 +3656,8 @@
      * Unregister all listeners.
      */
     public void destroy() {
-        // TODO: inject these dependencies:
-        TelephonyManager telephony =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephony != null) {
-            mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
-        }
-
+        mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
 
         if (mDeviceProvisionedObserver != null) {
@@ -3714,8 +3687,9 @@
         mHandler.removeCallbacksAndMessages(null);
     }
 
+    @SuppressLint("MissingPermission")
     @Override
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index efa5558..b793fd2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -66,10 +66,13 @@
         listView.setDividerHeight(mContext.getResources().getDimensionPixelSize(
                 R.dimen.bouncer_user_switcher_popup_divider_height));
 
-        int height  = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bouncer_user_switcher_popup_header_height);
-        listView.addHeaderView(createSpacer(height), null, false);
-        listView.addFooterView(createSpacer(height), null, false);
+        if (listView.getTag(R.id.header_footer_views_added_tag_key) == null) {
+            int height = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.bouncer_user_switcher_popup_header_height);
+            listView.addHeaderView(createSpacer(height), null, false);
+            listView.addFooterView(createSpacer(height), null, false);
+            listView.setTag(R.id.header_footer_views_added_tag_key, new Object());
+        }
 
         listView.setOnTouchListener((v, ev) -> {
             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 8293c74..3ea8826 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -24,10 +24,10 @@
 
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 
 /**
  *  Interface to control Keyguard View. It should be implemented by KeyguardViewManagers, which
@@ -185,7 +185,7 @@
      */
     void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             NotificationPanelViewController notificationPanelViewController,
-            @Nullable PanelExpansionStateManager panelExpansionStateManager,
+            @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer,
             KeyguardBypassController bypassController);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
new file mode 100644
index 0000000..50012a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging
+
+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.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.dagger.KeyguardLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardLog"
+
+/**
+ * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
+ * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
+ * an overkill.
+ */
+class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
+    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+    fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+
+    fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+    fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
+
+    private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
+        buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarCalculatedAlpha(alpha: Float) {
+        debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarExplicitAlpha(alpha: Float) {
+        debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
+        debugLog(
+            {
+                int1 = visibility
+                double1 = alpha.toDouble()
+                str1 = state
+            },
+            { "changing visibility to $int1 with alpha $double1 in state: $str1" }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 7a00cd9..bf9f4c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
 
     fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
 
     fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 5f586c9..a21f45f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,7 +96,9 @@
                     .setStartingSurface(mWMComponent.getStartingSurface())
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
-                    .setBackAnimation(mWMComponent.getBackAnimation());
+                    .setBackAnimation(mWMComponent.getBackAnimation())
+                    .setFloatingTasks(mWMComponent.getFloatingTasks())
+                    .setDesktopMode(mWMComponent.getDesktopMode());
 
             // Only initialize when not starting from tests since this currently initializes some
             // components that shouldn't be run in the test environment
@@ -115,7 +117,9 @@
                     .setDisplayAreaHelper(Optional.ofNullable(null))
                     .setStartingSurface(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
-                    .setBackAnimation(Optional.ofNullable(null));
+                    .setBackAnimation(Optional.ofNullable(null))
+                    .setFloatingTasks(Optional.ofNullable(null))
+                    .setDesktopMode(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
         if (initializeComponents) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
index 2ba2bb6..ed6fbec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -45,7 +45,7 @@
     private boolean mShouldSetTouchStart;
 
     @Nullable private MoveWindowTask mMoveWindowTask;
-    private PointF mLastDrag = new PointF();
+    private final PointF mLastDrag = new PointF();
     private final Handler mHandler;
 
     SimpleMirrorWindowControl(Context context, Handler handler) {
@@ -92,8 +92,7 @@
     }
 
     private Point findOffset(View v, int moveFrameAmount) {
-        final Point offset = mTmpPoint;
-        offset.set(0, 0);
+        mTmpPoint.set(0, 0);
         if (v.getId() == R.id.left_control) {
             mTmpPoint.x = -moveFrameAmount;
         } else if (v.getId() == R.id.up_control) {
@@ -184,7 +183,7 @@
         private final int mYOffset;
         private final Handler mHandler;
         /** Time in milliseconds between successive task executions.*/
-        private long mPeriod;
+        private final long mPeriod;
         private boolean mCancel;
 
         MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index e3c04a3..a6e767c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -104,7 +104,7 @@
     private final Context mContext;
     private final Resources mResources;
     private final Handler mHandler;
-    private Rect mWindowBounds;
+    private final Rect mWindowBounds;
     private final int mDisplayId;
     @Surface.Rotation
     @VisibleForTesting
@@ -193,11 +193,11 @@
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private final MagnificationGestureDetector mGestureDetector;
     private final int mBounceEffectDuration;
-    private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
+    private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
     private Locale mLocale;
     private NumberFormat mPercentFormat;
     private float mBounceEffectAnimationScale;
-    private SysUiState mSysUiState;
+    private final SysUiState mSysUiState;
     // Set it to true when the view is overlapped with the gesture insets at the bottom.
     private boolean mOverlapWithGestureInsets;
     private boolean mIsDragging;
@@ -215,7 +215,7 @@
     private boolean mEditSizeEnable = false;
 
     @Nullable
-    private MirrorWindowControl mMirrorWindowControl;
+    private final MirrorWindowControl mMirrorWindowControl;
 
     WindowMagnificationController(
             @UiContext Context context,
@@ -562,9 +562,7 @@
     /** Returns the rotation degree change of two {@link Surface.Rotation} */
     private int getDegreeFromRotation(@Surface.Rotation int newRotation,
                                       @Surface.Rotation int oldRotation) {
-        final int rotationDiff = oldRotation - newRotation;
-        final int degree = (rotationDiff + 4) % 4 * 90;
-        return degree;
+        return (oldRotation - newRotation + 4) % 4 * 90;
     }
 
     private void createMirrorWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb7..2cca086 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -775,6 +775,12 @@
         }
         mContainerState = STATE_ANIMATING_OUT;
 
+        // Request hiding soft-keyboard before animating away credential UI, in case IME insets
+        // animation get delayed by dismissing animation.
+        if (isAttachedToWindow() && getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+            getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+
         if (sendReason) {
             mPendingCallbackReason = reason;
         } else {
@@ -875,6 +881,7 @@
     static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
         final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 | WindowManager.LayoutParams.FLAG_SECURE
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 29b0088..1ceb6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -373,18 +373,14 @@
 
     @Override
     public void onTryAgainPressed(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onTryAgainPressed: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onTryAgainPressed");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onTryAgainPressed");
             return;
         }
 
         try {
-            mReceiver.onTryAgainPressed();
+            receiver.onTryAgainPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling try again", e);
         }
@@ -392,18 +388,14 @@
 
     @Override
     public void onDeviceCredentialPressed(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onDeviceCredentialPressed: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onDeviceCredentialPressed");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onDeviceCredentialPressed");
             return;
         }
 
         try {
-            mReceiver.onDeviceCredentialPressed();
+            receiver.onDeviceCredentialPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling credential button", e);
         }
@@ -411,18 +403,14 @@
 
     @Override
     public void onSystemEvent(int event, long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onSystemEvent");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onSystemEvent");
             return;
         }
 
         try {
-            mReceiver.onSystemEvent(event);
+            receiver.onSystemEvent(event);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when sending system event", e);
         }
@@ -430,23 +418,46 @@
 
     @Override
     public void onDialogAnimatedIn(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onDialogAnimatedIn: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onDialogAnimatedIn");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onDialogAnimatedIn");
             return;
         }
 
         try {
-            mReceiver.onDialogAnimatedIn();
+            receiver.onDialogAnimatedIn();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
         }
     }
 
+    @Nullable
+    private IBiometricSysuiReceiver getCurrentReceiver(long requestId) {
+        if (!isRequestIdValid(requestId)) {
+            return null;
+        }
+
+        if (mReceiver == null) {
+            Log.w(TAG, "getCurrentReceiver: Receiver is null");
+        }
+
+        return mReceiver;
+    }
+
+    private boolean isRequestIdValid(long requestId) {
+        if (mCurrentDialog == null) {
+            Log.w(TAG, "shouldNotifyReceiver: dialog already gone");
+            return false;
+        }
+
+        if (requestId != mCurrentDialog.getRequestId()) {
+            Log.w(TAG, "shouldNotifyReceiver: requestId doesn't match");
+            return false;
+        }
+
+        return true;
+    }
+
     @Override
     public void onDismissed(@DismissedReason int reason,
                             @Nullable byte[] credentialAttestation, long requestId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 3ad2bef..4130cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -22,9 +22,9 @@
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.util.ViewController
 import java.io.PrintWriter
 
@@ -41,7 +41,7 @@
 abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
     view: T,
     protected val statusBarStateController: StatusBarStateController,
-    protected val panelExpansionStateManager: PanelExpansionStateManager,
+    protected val shadeExpansionStateManager: ShadeExpansionStateManager,
     protected val dialogManager: SystemUIDialogManager,
     private val dumpManager: DumpManager
 ) : ViewController<T>(view), Dumpable {
@@ -54,7 +54,7 @@
     private var dialogAlphaAnimator: ValueAnimator? = null
     private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
 
-    private val panelExpansionListener = PanelExpansionListener { event ->
+    private val shadeExpansionListener = ShadeExpansionListener { event ->
         // Notification shade can be expanded but not visible (fraction: 0.0), for example
         // when a heads-up notification (HUN) is showing.
         notificationShadeVisible = event.expanded && event.fraction > 0f
@@ -108,13 +108,13 @@
     }
 
     override fun onViewAttached() {
-        panelExpansionStateManager.addExpansionListener(panelExpansionListener)
+        shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
         dialogManager.registerListener(dialogListener)
         dumpManager.registerDumpable(dumpTag, this)
     }
 
     override fun onViewDetached() {
-        panelExpansionStateManager.removeExpansionListener(panelExpansionListener)
+        shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
         dialogManager.unregisterListener(dialogListener)
         dumpManager.unregisterDumpable(dumpTag)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 4cd40d2..e6aeb43 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -17,8 +17,8 @@
 
 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 com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 
 /**
  * Class that coordinates non-HBM animations for biometric prompt.
@@ -26,13 +26,13 @@
 class UdfpsBpViewController(
     view: UdfpsBpView,
     statusBarStateController: StatusBarStateController,
-    panelExpansionStateManager: PanelExpansionStateManager,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsBpView>(
     view,
     statusBarStateController,
-    panelExpansionStateManager,
+    shadeExpansionStateManager,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 27e9af9..f6dedd9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -63,12 +63,12 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -111,7 +111,7 @@
     private final WindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
     @NonNull private final Executor mBiometricExecutor;
-    @NonNull private final PanelExpansionStateManager mPanelExpansionStateManager;
+    @NonNull private final ShadeExpansionStateManager mShadeExpansionStateManager;
     @NonNull private final StatusBarStateController mStatusBarStateController;
     @NonNull private final KeyguardStateController mKeyguardStateController;
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@@ -205,7 +205,7 @@
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
                             mWindowManager, mAccessibilityManager, mStatusBarStateController,
-                            mPanelExpansionStateManager, mKeyguardViewManager,
+                            mShadeExpansionStateManager, mKeyguardViewManager,
                             mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
                             mLockscreenShadeTransitionController, mConfigurationController,
                             mSystemClock, mKeyguardStateController,
@@ -582,7 +582,7 @@
             @NonNull WindowManager windowManager,
             @NonNull StatusBarStateController statusBarStateController,
             @Main DelayableExecutor fgExecutor,
-            @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull DumpManager dumpManager,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -615,7 +615,7 @@
         mFingerprintManager = checkNotNull(fingerprintManager);
         mWindowManager = windowManager;
         mFgExecutor = fgExecutor;
-        mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeExpansionStateManager = shadeExpansionStateManager;
         mStatusBarStateController = statusBarStateController;
         mKeyguardStateController = keyguardStateController;
         mKeyguardViewManager = statusBarKeyguardViewManager;
@@ -912,6 +912,12 @@
         if (view.isDisplayConfigured()) {
             view.unconfigureDisplay();
         }
+
+        if (mCancelAodTimeoutAction != null) {
+            mCancelAodTimeoutAction.run();
+            mCancelAodTimeoutAction = null;
+        }
+        mIsAodInterruptActive = false;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 1c62f8a..66a521c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -43,11 +43,11 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
@@ -67,7 +67,7 @@
     private val windowManager: WindowManager,
     private val accessibilityManager: AccessibilityManager,
     private val statusBarStateController: StatusBarStateController,
-    private val panelExpansionStateManager: PanelExpansionStateManager,
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dialogManager: SystemUIDialogManager,
@@ -192,7 +192,7 @@
                     },
                     enrollHelper ?: throw IllegalStateException("no enrollment helper"),
                     statusBarStateController,
-                    panelExpansionStateManager,
+                    shadeExpansionStateManager,
                     dialogManager,
                     dumpManager,
                     overlayParams.scaleFactor
@@ -202,7 +202,7 @@
                 UdfpsKeyguardViewController(
                     view.addUdfpsView(R.layout.udfps_keyguard_view),
                     statusBarStateController,
-                    panelExpansionStateManager,
+                    shadeExpansionStateManager,
                     statusBarKeyguardViewManager,
                     keyguardUpdateMonitor,
                     dumpManager,
@@ -221,7 +221,7 @@
                 UdfpsBpViewController(
                     view.addUdfpsView(R.layout.udfps_bp_view),
                     statusBarStateController,
-                    panelExpansionStateManager,
+                    shadeExpansionStateManager,
                     dialogManager,
                     dumpManager
                 )
@@ -231,7 +231,7 @@
                 UdfpsFpmOtherViewController(
                     view.addUdfpsView(R.layout.udfps_fpm_other_view),
                     statusBarStateController,
-                    panelExpansionStateManager,
+                    shadeExpansionStateManager,
                     dialogManager,
                     dumpManager
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 0b7bdde..e01273f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -22,8 +22,8 @@
 import com.android.systemui.R;
 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 com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 
 /**
  * Class that coordinates non-HBM animations during enrollment.
@@ -54,11 +54,11 @@
             @NonNull UdfpsEnrollView view,
             @NonNull UdfpsEnrollHelper enrollHelper,
             @NonNull StatusBarStateController statusBarStateController,
-            @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager,
             float scaleFactor) {
-        super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+        super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
                 dumpManager);
         mEnrollProgressBarRadius = (int) (scaleFactor * getContext().getResources().getInteger(
                 R.integer.config_udfpsEnrollProgressBar));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
index 98205cf..7c23278 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
@@ -17,8 +17,8 @@
 
 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 com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 
 /**
  * Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt
@@ -29,13 +29,13 @@
 class UdfpsFpmOtherViewController(
     view: UdfpsFpmOtherView,
     statusBarStateController: StatusBarStateController,
-    panelExpansionStateManager: PanelExpansionStateManager,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsFpmOtherView>(
     view,
     statusBarStateController,
-    panelExpansionStateManager,
+    shadeExpansionStateManager,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 24b8933..934aedf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -31,6 +31,9 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -38,9 +41,6 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.time.SystemClock;
@@ -88,7 +88,7 @@
     protected UdfpsKeyguardViewController(
             @NonNull UdfpsKeyguardView view,
             @NonNull StatusBarStateController statusBarStateController,
-            @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
             @NonNull DumpManager dumpManager,
@@ -100,7 +100,7 @@
             @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull UdfpsController udfpsController,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
-        super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+        super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
                 dumpManager);
         mKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -153,7 +153,7 @@
         mQsExpansion = mKeyguardViewManager.getQsExpansion();
         updateGenericBouncerVisibility();
         mConfigurationController.addCallback(mConfigurationListener);
-        getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener);
+        getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
         updateScaleFactor();
         mView.updatePadding();
         updateAlpha();
@@ -174,7 +174,7 @@
         mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
         mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
         mConfigurationController.removeCallback(mConfigurationListener);
-        getPanelExpansionStateManager().removeExpansionListener(mPanelExpansionListener);
+        getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
         if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
             mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
         }
@@ -502,9 +502,9 @@
                 }
             };
 
-    private final PanelExpansionListener mPanelExpansionListener = new PanelExpansionListener() {
+    private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
         @Override
-        public void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+        public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
             float fraction = event.getFraction();
             mPanelExpansionFraction =
                     mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index bfbf37a..d53e56f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -220,7 +220,7 @@
             return r;
         }).collect(Collectors.toList());
 
-        logDebug("False Gesture: " + localResult[0]);
+        logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
 
         return localResult[0];
     }
@@ -454,6 +454,12 @@
         }
     }
 
+    static void logVerbose(String msg) {
+        if (DEBUG) {
+            Log.v(TAG, msg);
+        }
+    }
+
     static void logInfo(String msg) {
         Log.i(TAG, msg);
         RECENT_INFO_LOG.add(msg);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index f18413b..701df89 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -42,8 +42,10 @@
     public static final int QS_COLLAPSE = 12;
     public static final int UDFPS_AUTHENTICATION = 13;
     public static final int LOCK_ICON = 14;
-    public static final int QS_SWIPE = 15;
+    public static final int QS_SWIPE_SIDE = 15;
     public static final int BACK_GESTURE = 16;
+    public static final int QS_SWIPE_NESTED = 17;
+    public static final int MEDIA_SEEKBAR = 18;
 
     @IntDef({
             QUICK_SETTINGS,
@@ -62,8 +64,10 @@
             BRIGHTNESS_SLIDER,
             UDFPS_AUTHENTICATION,
             LOCK_ICON,
-            QS_SWIPE,
-            BACK_GESTURE
+            QS_SWIPE_SIDE,
+            QS_SWIPE_NESTED,
+            BACK_GESTURE,
+            MEDIA_SEEKBAR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InteractionType {}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index d0fe1c3..f8ee49a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -23,7 +23,9 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
 import android.provider.DeviceConfig;
@@ -152,11 +154,13 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == MEDIA_SEEKBAR
                 || interactionType == SHADE_DRAG
                 || interactionType == QS_COLLAPSE
                 || interactionType == Classifier.UDFPS_AUTHENTICATION
                 || interactionType == Classifier.LOCK_ICON
-                || interactionType == Classifier.QS_SWIPE) {
+                || interactionType == Classifier.QS_SWIPE_SIDE
+                || interactionType == QS_SWIPE_NESTED) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
index d757528..d18d62f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
@@ -148,6 +148,11 @@
     }
 
     /** */
+    public static void logVerbose(String msg) {
+        BrightLineFalsingManager.logVerbose(msg);
+    }
+
+    /** */
     public static void logInfo(String msg) {
         BrightLineFalsingManager.logInfo(msg);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index a3ecb0c..3991a35 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -78,10 +78,10 @@
 
     void onMotionEvent(MotionEvent motionEvent) {
         List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
-        FalsingClassifier.logDebug("Unpacked into: " + motionEvents.size());
+        FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size());
         if (BrightLineFalsingManager.DEBUG) {
             for (MotionEvent m : motionEvents) {
-                FalsingClassifier.logDebug(
+                FalsingClassifier.logVerbose(
                         "x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime());
             }
         }
@@ -92,7 +92,7 @@
         }
         mRecentMotionEvents.addAll(motionEvents);
 
-        FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size());
+        FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
 
         mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 32d9ca59..e8c83b1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -18,8 +18,9 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
-import static com.android.systemui.classifier.Classifier.QS_SWIPE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 
 import android.provider.DeviceConfig;
@@ -119,7 +120,8 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER
-                || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE) {
+                || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE
+                || interactionType == MEDIA_SEEKBAR) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index f040712..f576a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -20,11 +20,13 @@
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
 import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
-import static com.android.systemui.classifier.Classifier.QS_SWIPE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
@@ -86,7 +88,14 @@
             case QS_COLLAPSE:
                 wrongDirection = !vertical || !up;
                 break;
-            case QS_SWIPE:
+            case QS_SWIPE_SIDE:
+                wrongDirection = vertical;
+                break;
+            case QS_SWIPE_NESTED:
+                wrongDirection = !vertical;
+                break;
+            case MEDIA_SEEKBAR:
+                confidence = 0;
                 wrongDirection = vertical;
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index 40c28fa..840982c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -22,6 +22,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
 import android.graphics.Point;
@@ -91,6 +92,7 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == MEDIA_SEEKBAR
                 || interactionType == SHADE_DRAG
                 || interactionType == LOCK_ICON) {
             return Result.passed(0);
@@ -137,8 +139,8 @@
             runningAbsDy += Math.abs(point.y - pY);
             pX = point.x;
             pY = point.y;
-            logDebug("(x, y, runningAbsDx, runningAbsDy) - (" + pX + ", " + pY + ", " + runningAbsDx
-                    + ", " + runningAbsDy + ")");
+            logVerbose("(x, y, runningAbsDx, runningAbsDy) - ("
+                    + pX + ", " + pY + ", " + runningAbsDx + ", " + runningAbsDy + ")");
         }
 
         float devianceX = runningAbsDx - actualDx;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index 3d5e601..e342ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -47,7 +47,8 @@
             shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri());
             shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         } else {
-            shareIntent.putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context));
+            shareIntent.putExtra(
+                    Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString());
             shareIntent.setType("text/plain");
         }
         Intent chooserIntent = Intent.createChooser(shareIntent, null)
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
deleted file mode 100644
index d6a059d..0000000
--- a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
+++ /dev/null
@@ -1,27 +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.containeddrawable
-
-import android.graphics.drawable.Drawable
-import androidx.annotation.DrawableRes
-
-/** Convenience container for [Drawable] or a way to load it later. */
-sealed class ContainedDrawable {
-    data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
-    data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index b8a0013..1f7021e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.AnyThread
 import android.annotation.MainThread
+import android.app.Activity
 import android.app.AlertDialog
 import android.app.Dialog
 import android.app.PendingIntent
@@ -119,8 +120,16 @@
     }
 
     override fun closeDialogs() {
-        dialog?.dismiss()
-        dialog = null
+        val isActivityFinishing =
+            (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
+        if (isActivityFinishing == true) {
+            dialog = null
+            return
+        }
+        if (dialog?.isShowing == true) {
+            dialog?.dismiss()
+            dialog = null
+        }
     }
 
     override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 4096ed4..139a8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -46,6 +46,7 @@
 import android.content.res.Resources;
 import android.hardware.SensorManager;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.camera2.CameraManager;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -237,22 +238,39 @@
     @Singleton
     static IDreamManager provideIDreamManager() {
         return IDreamManager.Stub.asInterface(
-                ServiceManager.checkService(DreamService.DREAM_SERVICE));
+                ServiceManager.getService(DreamService.DREAM_SERVICE));
     }
 
     @Provides
     @Singleton
     @Nullable
     static FaceManager provideFaceManager(Context context) {
-        return context.getSystemService(FaceManager.class);
-
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return context.getSystemService(FaceManager.class);
+        }
+        return null;
     }
 
     @Provides
     @Singleton
     @Nullable
     static FingerprintManager providesFingerprintManager(Context context) {
-        return context.getSystemService(FingerprintManager.class);
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return context.getSystemService(FingerprintManager.class);
+        }
+        return null;
+    }
+
+    /**
+     * @return null if both faceManager and fingerprintManager are null.
+     */
+    @Provides
+    @Singleton
+    @Nullable
+    static BiometricManager providesBiometricManager(Context context,
+            @Nullable FaceManager faceManager, @Nullable FingerprintManager fingerprintManager) {
+        return faceManager == null && fingerprintManager == null ? null :
+                context.getSystemService(BiometricManager.class);
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 029cabb..0d06c51 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -40,7 +40,9 @@
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -109,6 +111,12 @@
         @BindsInstance
         Builder setBackAnimation(Optional<BackAnimation> b);
 
+        @BindsInstance
+        Builder setFloatingTasks(Optional<FloatingTasks> f);
+
+        @BindsInstance
+        Builder setDesktopMode(Optional<DesktopMode> d);
+
         SysUIComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0469152..06dbab9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,6 +41,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.keyguard.data.BouncerViewModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.media.dagger.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
@@ -80,6 +81,7 @@
 import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
 import com.android.systemui.statusbar.window.StatusBarWindowModule;
+import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
 import com.android.systemui.tuner.dagger.TunerModule;
 import com.android.systemui.unfold.SysUIUnfoldModule;
 import com.android.systemui.user.UserModule;
@@ -116,6 +118,7 @@
             AppOpsModule.class,
             AssistModule.class,
             BiometricsModule.class,
+            BouncerViewModule.class,
             ClockModule.class,
             CoroutinesModule.class,
             DreamModule.class,
@@ -143,6 +146,7 @@
             StatusBarWindowModule.class,
             SysUIConcurrencyModule.class,
             SysUIUnfoldModule.class,
+            TelephonyRepositoryModule.class,
             TunerModule.class,
             UserModule.class,
             UtilModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b6923a8..096f969 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,8 +23,6 @@
 
 import com.android.systemui.SystemUIInitializerFactory;
 import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -32,7 +30,9 @@
 import com.android.wm.shell.dagger.TvWMShellModule;
 import com.android.wm.shell.dagger.WMShellModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -110,4 +110,13 @@
 
     @WMSingleton
     Optional<BackAnimation> getBackAnimation();
+
+    @WMSingleton
+    Optional<FloatingTasks> getFloatingTasks();
+
+    /**
+     * Optional {@link DesktopMode} component for interacting with desktop mode.
+     */
+    @WMSingleton
+    Optional<DesktopMode> getDesktopMode();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index b598554..4c4aa5c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -33,6 +33,18 @@
     boolean isProvisioned();
 
     /**
+     * Whether there's a pulse that's been requested but hasn't started transitioning to pulsing
+     * states yet.
+     */
+    boolean isPulsePending();
+
+    /**
+     * @param isPulsePending whether a pulse has been requested but hasn't started transitioning
+     *                       to the pulse state yet
+     */
+    void setPulsePending(boolean isPulsePending);
+
+    /**
      * Makes a current pulse last for twice as long.
      * @param reason why we're extending it.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4161cf6..8ae305b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -280,8 +280,8 @@
     /**
      * Appends pulse dropped event to logs
      */
-    public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
-        mLogger.logPulseDropped(pulsePending, state, blocked);
+    public void tracePulseDropped(String from, DozeMachine.State state) {
+        mLogger.logPulseDropped(from, state);
     }
 
     /**
@@ -292,6 +292,13 @@
     }
 
     /**
+     * Appends pulsing event to logs.
+     */
+    public void tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason) {
+        mLogger.logPulseEvent(pulseEvent, dozing, DozeLog.reasonToString(pulseReason));
+    }
+
+    /**
      * Appends pulse dropped event to logs
      * @param reason why the pulse was dropped
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4b279ec..21a2c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -224,13 +224,12 @@
         })
     }
 
-    fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
+    fun logPulseDropped(from: String, state: DozeMachine.State) {
         buffer.log(TAG, INFO, {
-            bool1 = pulsePending
-            str1 = state.name
-            bool2 = blocked
+            str1 = from
+            str2 = state.name
         }, {
-            "Pulse dropped, pulsePending=$bool1 state=$str1 blocked=$bool2"
+            "Pulse dropped, cannot pulse from=$str1 state=$str2"
         })
     }
 
@@ -243,6 +242,16 @@
         })
     }
 
+    fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = pulseEvent
+            bool1 = dozing
+            str2 = pulseReason
+        }, {
+            "Pulse-$str1 dozing=$bool1 pulseReason=$str2"
+        })
+    }
+
     fun logPulseDropped(reason: String) {
         buffer.log(TAG, INFO, {
             str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00ac8bc..ef454ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -102,7 +102,6 @@
     private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
-    private boolean mPulsePending;
     private Runnable mAodInterruptRunnable;
 
     /** see {@link #onProximityFar} prox for callback */
@@ -303,8 +302,8 @@
                         null /* onPulseSuppressedListener */);
             }
         } else {
-            proximityCheckThenCall((result) -> {
-                if (result != null && result) {
+            proximityCheckThenCall((isNear) -> {
+                if (isNear != null && isNear) {
                     // In pocket, drop event.
                     mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
                     return;
@@ -410,8 +409,8 @@
         sWakeDisplaySensorState = wake;
 
         if (wake) {
-            proximityCheckThenCall((result) -> {
-                if (result != null && result) {
+            proximityCheckThenCall((isNear) -> {
+                if (isNear != null && isNear) {
                     // In pocket, drop event.
                     return;
                 }
@@ -537,24 +536,44 @@
             return;
         }
 
-        if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
-            if (mAllowPulseTriggers) {
-                mDozeLog.tracePulseDropped(mPulsePending, dozeState, mDozeHost.isPulsingBlocked());
+        if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+            if (!mAllowPulseTriggers) {
+                mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
+            } else if (mDozeHost.isPulsePending()) {
+                mDozeLog.tracePulseDropped("requestPulse - pulsePending");
+            } else if (!canPulse()) {
+                mDozeLog.tracePulseDropped("requestPulse", dozeState);
             }
             runIfNotNull(onPulseSuppressedListener);
             return;
         }
 
-        mPulsePending = true;
-        proximityCheckThenCall((result) -> {
-            if (result != null && result) {
+        mDozeHost.setPulsePending(true);
+        proximityCheckThenCall((isNear) -> {
+            if (isNear != null && isNear) {
                 // in pocket, abort pulse
-                mDozeLog.tracePulseDropped("inPocket");
-                mPulsePending = false;
+                mDozeLog.tracePulseDropped("requestPulse - inPocket");
+                mDozeHost.setPulsePending(false);
                 runIfNotNull(onPulseSuppressedListener);
             } else {
                 // not in pocket, continue pulsing
-                continuePulseRequest(reason);
+                final boolean isPulsePending = mDozeHost.isPulsePending();
+                mDozeHost.setPulsePending(false);
+                if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+                    if (!isPulsePending) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+                                + " pending, pulse was cancelled before it could start"
+                                + " transitioning to pulsing state.");
+                    } else if (mDozeHost.isPulsingBlocked()) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
+                    } else if (!canPulse()) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+                    }
+                    runIfNotNull(onPulseSuppressedListener);
+                    return;
+                }
+
+                mMachine.requestPulse(reason);
             }
         }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
 
@@ -569,16 +588,6 @@
                 || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
     }
 
-    private void continuePulseRequest(int reason) {
-        mPulsePending = false;
-        if (mDozeHost.isPulsingBlocked() || !canPulse()) {
-            mDozeLog.tracePulseDropped(mPulsePending, mMachine.getState(),
-                    mDozeHost.isPulsingBlocked());
-            return;
-        }
-        mMachine.requestPulse(reason);
-    }
-
     @Nullable
     private InstanceId getKeyguardSessionId() {
         return mSessionTracker.getSessionId(SESSION_KEYGUARD);
@@ -591,7 +600,7 @@
         pw.print(" notificationPulseTime=");
         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
 
-        pw.println(" pulsePending=" + mPulsePending);
+        pw.println(" DozeHost#isPulsePending=" + mDozeHost.isPulsePending());
         pw.println("DozeSensors:");
         IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
         idpw.increaseIndent();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d7b7777..733a80d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -35,6 +35,7 @@
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -73,6 +74,7 @@
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
     private final int mDreamOverlayMaxTranslationY;
+    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
 
     private long mJitterStartTimeMillis;
 
@@ -131,7 +133,8 @@
             @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
                     burnInProtectionUpdateInterval,
-            @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) {
+            @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
+            BouncerCallbackInteractor bouncerCallbackInteractor) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
@@ -151,6 +154,7 @@
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
         mMillisUntilFullJitter = millisUntilFullJitter;
+        mBouncerCallbackInteractor = bouncerCallbackInteractor;
     }
 
     @Override
@@ -167,6 +171,7 @@
         if (bouncer != null) {
             bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
         }
+        mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
     }
 
     @Override
@@ -176,6 +181,7 @@
         if (bouncer != null) {
             bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         }
+        mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
     }
 
     View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 96f77b3..696fc72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dreams;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
 import android.util.Log;
@@ -26,11 +27,13 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.lifecycle.ViewModelStore;
 
+import com.android.dream.lowlight.dagger.LowLightDreamModule;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
@@ -44,6 +47,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * The {@link DreamOverlayService} is responsible for placing an overlay on top of a dream. The
@@ -62,6 +66,8 @@
     // content area).
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Nullable
+    private final ComponentName mLowLightDreamComponent;
     private final UiEventLogger mUiEventLogger;
 
     // A reference to the {@link Window} used to hold the dream overlay.
@@ -125,10 +131,13 @@
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
+                    ComponentName lowLightDreamComponent) {
         mContext = context;
         mExecutor = executor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLowLightDreamComponent = lowLightDreamComponent;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
@@ -155,6 +164,7 @@
             windowManager.removeView(mWindow.getDecorView());
         }
         mStateController.setOverlayActive(false);
+        mStateController.setLowLightActive(false);
         mDestroyed = true;
         super.onDestroy();
     }
@@ -163,6 +173,9 @@
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
         mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
         setCurrentState(Lifecycle.State.STARTED);
+        final ComponentName dreamComponent = getDreamComponent();
+        mStateController.setLowLightActive(
+                dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
         mExecutor.execute(() -> {
             if (mDestroyed) {
                 // The task could still be executed after the service has been destroyed. Bail if
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 69e41ba..72feaca 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -50,6 +50,7 @@
     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;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -193,6 +194,14 @@
         return containsState(STATE_DREAM_OVERLAY_ACTIVE);
     }
 
+    /**
+     * Returns whether low light mode is active.
+     * @return {@code true} if in low light mode, {@code false} otherwise.
+     */
+    public boolean isLowLightActive() {
+        return containsState(STATE_LOW_LIGHT_ACTIVE);
+    }
+
     private boolean containsState(int state) {
         return (mState & state) != 0;
     }
@@ -222,6 +231,14 @@
     }
 
     /**
+     * Sets whether low light mode is active.
+     * @param active {@code true} if low light mode is active, {@code false} otherwise.
+     */
+    public void setLowLightActive(boolean active) {
+        modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
+    }
+
+    /**
      * Returns the available complication types.
      */
     @Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index aa59cc6..bb1c430 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -20,7 +20,6 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 
-import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.StatusBarManager;
 import android.content.res.Resources;
@@ -36,6 +35,8 @@
 import android.util.PluralsMessageFormatter;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
@@ -73,6 +74,8 @@
     private final Optional<DreamOverlayNotificationCountProvider>
             mDreamOverlayNotificationCountProvider;
     private final ZenModeController mZenModeController;
+    private final DreamOverlayStateController mDreamOverlayStateController;
+    private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
     private final Executor mMainExecutor;
     private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
@@ -102,6 +105,14 @@
         }
     };
 
+    private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+            new DreamOverlayStateController.Callback() {
+                @Override
+                public void onStateChanged() {
+                    updateLowLightState();
+                }
+            };
+
     private final IndividualSensorPrivacyController.Callback mSensorCallback =
             (sensor, blocked) -> updateMicCameraBlockedStatusIcon();
 
@@ -140,7 +151,8 @@
             Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
             ZenModeController zenModeController,
             StatusBarWindowStateController statusBarWindowStateController,
-            DreamOverlayStatusBarItemsProvider statusBarItemsProvider) {
+            DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
+            DreamOverlayStateController dreamOverlayStateController) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -151,8 +163,10 @@
         mDateFormatUtil = dateFormatUtil;
         mSensorPrivacyController = sensorPrivacyController;
         mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider;
+        mStatusBarWindowStateController = statusBarWindowStateController;
         mStatusBarItemsProvider = statusBarItemsProvider;
         mZenModeController = zenModeController;
+        mDreamOverlayStateController = dreamOverlayStateController;
 
         // 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.
@@ -180,6 +194,9 @@
 
         mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
 
+        mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
+        updateLowLightState();
+
         mTouchInsetSession.addViewToTracking(mView);
     }
 
@@ -193,6 +210,7 @@
                 provider -> provider.removeCallback(mNotificationCountCallback));
         mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
         mView.removeAllExtraStatusBarItemViews();
+        mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
         mTouchInsetSession.clear();
 
         mIsAttached = false;
@@ -217,6 +235,15 @@
                 hasAlarm ? buildAlarmContentDescription(alarm) : null);
     }
 
+    private void updateLowLightState() {
+        int visibility = View.VISIBLE;
+        if (mDreamOverlayStateController.isLowLightActive()
+                || mStatusBarWindowStateController.windowIsShowing()) {
+            visibility = View.INVISIBLE;
+        }
+        mView.setVisibility(visibility);
+    }
+
     private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
         final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma";
         final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
@@ -272,7 +299,7 @@
 
     private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
         mMainExecutor.execute(() -> {
-            if (!mIsAttached) {
+            if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
                 return;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
deleted file mode 100644
index 789ebc5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
+++ /dev/null
@@ -1,70 +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.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextClock;
-
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows)
- */
-public class DoubleShadowTextClock extends TextClock {
-    private final DoubleShadowTextHelper mShadowHelper;
-
-    public DoubleShadowTextClock(Context context) {
-        this(context, null);
-    }
-
-    public DoubleShadowTextClock(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        final Resources resources = context.getResources();
-        final ShadowInfo keyShadowInfo = new ShadowInfo(
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color));
-
-        final ShadowInfo ambientShadowInfo = new ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_clock_ambient_text_shadow_radius),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color));
-        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        mShadowHelper.applyShadows(this, canvas, () -> {
-            super.onDraw(canvas);
-            return Unit.INSTANCE;
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
deleted file mode 100644
index cf7e312..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
+++ /dev/null
@@ -1,76 +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.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows}
- */
-public class DoubleShadowTextView extends TextView {
-    private final DoubleShadowTextHelper mShadowHelper;
-
-    public DoubleShadowTextView(Context context) {
-        this(context, null);
-    }
-
-    public DoubleShadowTextView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        final Resources resources = context.getResources();
-        final DoubleShadowTextHelper.ShadowInfo
-                keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_radius),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_dx),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color));
-
-        final DoubleShadowTextHelper.ShadowInfo
-                ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color));
-        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        mShadowHelper.applyShadows(this, canvas, () -> {
-            super.onDraw(canvas);
-            return Unit.INSTANCE;
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 2503d3c..821e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -28,6 +28,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -158,17 +161,38 @@
         private final Context mContext;
         private final ControlsComponent mControlsComponent;
 
+        private final UiEventLogger mUiEventLogger;
+
+        @VisibleForTesting
+        public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+            @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+            DREAM_HOME_CONTROLS_TAPPED(1212);
+
+            private final int mId;
+
+            DreamOverlayEvent(int id) {
+                mId = id;
+            }
+
+            @Override
+            public int getId() {
+                return mId;
+            }
+        }
+
         @Inject
         DreamHomeControlsChipViewController(
                 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
-                ControlsComponent controlsComponent) {
+                ControlsComponent controlsComponent,
+                UiEventLogger uiEventLogger) {
             super(view);
 
             mActivityStarter = activityStarter;
             mContext = context;
             mControlsComponent = controlsComponent;
+            mUiEventLogger = uiEventLogger;
         }
 
         @Override
@@ -184,6 +208,8 @@
         private void onClickHomeControls(View v) {
             if (DEBUG) Log.d(TAG, "home controls complication tapped");
 
+            mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+
             final Intent intent = new Intent(mContext, ControlsActivity.class)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
                     .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index 21a51d1..c07d402 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -18,13 +18,21 @@
 
 import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW;
 import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
 
+import android.app.PendingIntent;
 import android.util.Log;
 import android.view.View;
 
+import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.dream.MediaDreamComplication;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -87,6 +95,15 @@
 
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final MediaDreamComplication mMediaComplication;
+        private final MediaCarouselController mMediaCarouselController;
+
+        private final ActivityStarter mActivityStarter;
+        private final ActivityIntentHelper mActivityIntentHelper;
+        private final KeyguardStateController mKeyguardStateController;
+        private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+        private final FeatureFlags mFeatureFlags;
+        private boolean mIsTapToOpenEnabled;
 
         private boolean mMediaComplicationAdded;
 
@@ -94,15 +111,28 @@
         DreamMediaEntryViewController(
                 @Named(DREAM_MEDIA_ENTRY_VIEW) View view,
                 DreamOverlayStateController dreamOverlayStateController,
-                MediaDreamComplication mediaComplication) {
+                MediaDreamComplication mediaComplication,
+                MediaCarouselController mediaCarouselController,
+                ActivityStarter activityStarter,
+                ActivityIntentHelper activityIntentHelper,
+                KeyguardStateController keyguardStateController,
+                NotificationLockscreenUserManager lockscreenUserManager,
+                FeatureFlags featureFlags) {
             super(view);
             mDreamOverlayStateController = dreamOverlayStateController;
             mMediaComplication = mediaComplication;
+            mMediaCarouselController = mediaCarouselController;
+            mActivityStarter = activityStarter;
+            mActivityIntentHelper = activityIntentHelper;
+            mKeyguardStateController = keyguardStateController;
+            mLockscreenUserManager = lockscreenUserManager;
+            mFeatureFlags = featureFlags;
             mView.setOnClickListener(this::onClickMediaEntry);
         }
 
         @Override
         protected void onViewAttached() {
+            mIsTapToOpenEnabled = mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN);
         }
 
         @Override
@@ -113,6 +143,31 @@
         private void onClickMediaEntry(View v) {
             if (DEBUG) Log.d(TAG, "media entry complication tapped");
 
+            if (mIsTapToOpenEnabled) {
+                final PendingIntent clickIntent =
+                        mMediaCarouselController.getCurrentVisibleMediaContentIntent();
+
+                if (clickIntent == null) {
+                    return;
+                }
+
+                // See StatusBarNotificationActivityStarter#onNotificationClicked
+                final boolean showOverLockscreen = mKeyguardStateController.isShowing()
+                        && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(),
+                        mLockscreenUserManager.getCurrentUserId());
+
+                if (showOverLockscreen) {
+                    mActivityStarter.startActivity(clickIntent.getIntent(),
+                            /* dismissShade */ true,
+                            /* animationController */ null,
+                            /* showOverLockscreenWhenLocked */ true);
+                } else {
+                    mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, null);
+                }
+
+                return;
+            }
+
             if (!mMediaComplicationAdded) {
                 addMediaComplication();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 2dd2098..f9dca08 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import com.android.dream.lowlight.dagger.LowLightDreamModule;
 import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -37,6 +38,7 @@
  */
 @Module(includes = {
             RegisteredComplicationsModule.class,
+            LowLightDreamModule.class,
         },
         subcomponents = {
             DreamOverlayComponent.class,
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 f769a23..0dba4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -36,11 +36,11 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import java.util.Optional;
@@ -154,8 +154,8 @@
 
     private void setPanelExpansion(float expansion, float dragDownAmount) {
         mCurrentExpansion = expansion;
-        PanelExpansionChangeEvent event =
-                new PanelExpansionChangeEvent(
+        ShadeExpansionChangeEvent event =
+                new ShadeExpansionChangeEvent(
                         /* fraction= */ mCurrentExpansion,
                         /* expanded= */ false,
                         /* tracking= */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 2540035..2e46b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -68,6 +68,9 @@
 
     public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
 
+    public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
+            false);
+
     // next id: 112
 
     /***************************************/
@@ -101,8 +104,29 @@
     public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
 
     /** Whether UserSwitcherActivity should use modern architecture. */
-    public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
-            new UnreleasedFlag(209, true);
+    public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
+            new ReleasedFlag(209, true);
+
+    /**
+     * Whether the user interactor and repository should use `UserSwitcherController`.
+     *
+     * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
+     * the framework APIs.
+     */
+    public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
+            new UnreleasedFlag(210, true);
+
+    /**
+     * Whether `UserSwitcherController` should use the user interactor.
+     *
+     * <p>When this is {@code true}, the controller does not directly access framework APIs.
+     * Instead, it goes through the interactor.
+     *
+     * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
+     * {@code true} as it would created a cycle between controller -> interactor -> controller.
+     */
+    public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
+            new UnreleasedFlag(211, false);
 
     /***************************************/
     // 300 - power menu
@@ -192,11 +216,12 @@
 
     /***************************************/
     // 900 - media
-    public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
+    public static final UnreleasedFlag MEDIA_TAP_TO_TRANSFER = new UnreleasedFlag(900);
     public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
     public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
     public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
-    public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905);
+    public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
+    public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
 
     // 1000 - dock
     public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
@@ -234,6 +259,25 @@
     public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL =
             new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false);
 
+    @Keep
+    public static final SysPropBooleanFlag FLOATING_TASKS_ENABLED =
+            new SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false);
+
+    @Keep
+    public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
+            new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
+
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
+            new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
+            new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
+
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+            new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
     // 1200 - predictive back
     @Keep
     public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c135b3c..ad8c688 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -285,6 +285,14 @@
     var willUnlockWithInWindowLauncherAnimations: Boolean = false
 
     /**
+     * Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
+     * called [ILauncherUnlockAnimationController.playUnlockAnimation]. This is used exclusively for
+     * logging purposes to help track down bugs where the Launcher surface is prepared for unlock
+     * but then never animated.
+     */
+    private var launcherPreparedForUnlock = false
+
+    /**
      * Whether we decided in [prepareForInWindowLauncherAnimations] that we are able to and want to
      * play the smartspace shared element animation. If true,
      * [willUnlockWithInWindowLauncherAnimations] will also always be true since in-window
@@ -376,6 +384,20 @@
     }
 
     /**
+     * Logging helper to log the conditions under which we decide to perform the in-window
+     * animations. This is used if we prepare to unlock but then somehow decide later to not play
+     * the animation, which would leave Launcher in a bad state.
+     */
+    private fun logInWindowAnimationConditions() {
+        Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
+        Log.wtf(TAG, "  isNexusLauncherUnderneath: ${isNexusLauncherUnderneath()}")
+        Log.wtf(TAG, "  !notificationShadeWindowController.isLaunchingActivity: " +
+                "${!notificationShadeWindowController.isLaunchingActivity}")
+        Log.wtf(TAG, "  launcherUnlockController != null: ${launcherUnlockController != null}")
+        Log.wtf(TAG, "  !isFoldable(context): ${!isFoldable(context)}")
+    }
+
+    /**
      * Called from [KeyguardStateController] to let us know that the keyguard going away state has
      * changed.
      */
@@ -384,6 +406,15 @@
                 !statusBarStateController.leaveOpenOnKeyguardHide()) {
             prepareForInWindowLauncherAnimations()
         }
+
+        // If the keyguard is no longer going away and we were unlocking with in-window animations,
+        // make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
+        // against "tiny launcher" and similar states where the launcher is left in the prepared to
+        // animate state.
+        if (!keyguardStateController.isKeyguardGoingAway &&
+                willUnlockWithInWindowLauncherAnimations) {
+            launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+        }
     }
 
     /**
@@ -437,6 +468,8 @@
                 lockscreenSmartspaceBounds, /* lockscreenSmartspaceBounds */
                 selectedPage /* selectedPage */
             )
+
+            launcherPreparedForUnlock = true
         } catch (e: RemoteException) {
             Log.e(TAG, "Remote exception in prepareForInWindowUnlockAnimations.", e)
         }
@@ -495,6 +528,8 @@
                         true,
                         UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY,
                         0 /* startDelay */)
+
+                launcherPreparedForUnlock = false
             } else {
                 // Otherwise, we're swiping in an app and should just fade it in. The swipe gesture
                 // will translate it until the end of the swipe gesture.
@@ -554,6 +589,12 @@
                 surfaceBehindEntryAnimator.start()
             }
         }
+
+        if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
+            Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
+                    "in-window animation, however we apparently did not.")
+            logInWindowAnimationConditions()
+        }
     }
 
     /**
@@ -569,6 +610,8 @@
             LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
             CANNED_UNLOCK_START_DELAY /* startDelay */)
 
+        launcherPreparedForUnlock = false
+
         // Now that the Launcher surface (with its smartspace positioned identically to ours) is
         // visible, hide our smartspace.
         lockscreenSmartspace?.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 26db3ee4..5816f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -123,6 +124,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -133,7 +135,6 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -841,6 +842,8 @@
                     if (launchIsFullScreen) {
                         mCentralSurfaces.instantCollapseNotificationPanel();
                     }
+
+                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
                 }
 
                 @NonNull
@@ -987,6 +990,8 @@
                     setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
                     Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
                             + mOccluded);
+
+                    mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
                 }
 
                 @Override
@@ -995,6 +1000,9 @@
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
                     Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
+                    mInteractionJankMonitor.begin(
+                            createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION)
+                                    .setTag("UNOCCLUDE"));
                     setOccluded(false /* isOccluded */, true /* animate */);
 
                     if (apps == null || apps.length == 0 || apps[0] == null) {
@@ -1053,6 +1061,8 @@
                                 try {
                                     finishedCallback.onAnimationFinished();
                                     mUnoccludeAnimator = null;
+
+                                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
                                 } catch (RemoteException e) {
                                     e.printStackTrace();
                                 }
@@ -2563,7 +2573,8 @@
                         };
                 try {
                     mInteractionJankMonitor.begin(
-                            createInteractionJankMonitorConf("RunRemoteAnimation"));
+                            createInteractionJankMonitorConf(
+                                    CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RunRemoteAnimation"));
                     runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
                             wallpapers, nonApps, callback);
                 } catch (RemoteException e) {
@@ -2578,7 +2589,8 @@
                 mSurfaceBehindRemoteAnimationRunning = true;
 
                 mInteractionJankMonitor.begin(
-                        createInteractionJankMonitorConf("DismissPanel"));
+                        createInteractionJankMonitorConf(
+                                CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "DismissPanel"));
 
                 // Pass the surface and metadata to the unlock animation controller.
                 mKeyguardUnlockAnimationControllerLazy.get()
@@ -2586,7 +2598,8 @@
                                 apps, startTime, mSurfaceBehindRemoteAnimationRequested);
             } else {
                 mInteractionJankMonitor.begin(
-                        createInteractionJankMonitorConf("RemoteAnimationDisabled"));
+                        createInteractionJankMonitorConf(
+                                CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
                 mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
 
@@ -2666,10 +2679,15 @@
         sendUserPresentBroadcast();
     }
 
-    private Configuration.Builder createInteractionJankMonitorConf(String tag) {
-        return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
-                mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
-                .setTag(tag);
+    private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
+        return createInteractionJankMonitorConf(cuj, null /* tag */);
+    }
+
+    private Configuration.Builder createInteractionJankMonitorConf(int cuj, @Nullable String tag) {
+        final Configuration.Builder builder = Configuration.Builder.withView(
+                cuj, mKeyguardViewControllerLazy.get().getViewRootImpl().getView());
+
+        return tag != null ? builder.setTag(tag) : builder;
     }
 
     /**
@@ -2946,14 +2964,14 @@
      */
     public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
             NotificationPanelViewController panelView,
-            @Nullable PanelExpansionStateManager panelExpansionStateManager,
+            @Nullable ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer, KeyguardBypassController bypassController) {
         mCentralSurfaces = centralSurfaces;
         mKeyguardViewControllerLazy.get().registerCentralSurfaces(
                 centralSurfaces,
                 panelView,
-                panelExpansionStateManager,
+                shadeExpansionStateManager,
                 biometricUnlockController,
                 notificationContainer,
                 bypassController);
@@ -3280,6 +3298,10 @@
                 IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
             super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
 
+            mInteractionJankMonitor.begin(
+                    createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION)
+                            .setTag("OCCLUDE"));
+
             // This is the first signal we have from WM that we're going to be occluded. Set our
             // internal state to reflect that immediately, vs. waiting for the launch animator to
             // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
@@ -3296,6 +3318,7 @@
                     + "Setting occluded state to: " + isKeyguardOccluded);
             setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
 
+            mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
new file mode 100644
index 0000000..99ae85d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 com.android.systemui.dagger.SysUISingleton
+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()
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
similarity index 68%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
index 104bf99..390c54e 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
+package com.android.systemui.keyguard.data
 
-import android.app.cloudsearch.SearchRequest;
+import dagger.Binds
+import dagger.Module
 
-/**
- * Interface from the system to CloudSearch service.
- *
- * @hide
- */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
+@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/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
new file mode 100644
index 0000000..543389e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates app state for the lock screen bouncer. */
+@SysUISingleton
+class KeyguardBouncerRepository
+@Inject
+constructor(
+    private val viewMediatorCallback: ViewMediatorCallback,
+    keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    var bouncerPromptReason: Int? = null
+    /** Determines if we want to instantaneously show the bouncer instead of translating. */
+    private val _isScrimmed = MutableStateFlow(false)
+    val isScrimmed = _isScrimmed.asStateFlow()
+    /** Set amount of how much of the bouncer is showing on the screen */
+    private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+    val expansionAmount = _expansionAmount.asStateFlow()
+    private val _isVisible = MutableStateFlow(false)
+    val isVisible = _isVisible.asStateFlow()
+    private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
+    val show = _show.asStateFlow()
+    private val _showingSoon = MutableStateFlow(false)
+    val showingSoon = _showingSoon.asStateFlow()
+    private val _hide = MutableStateFlow(false)
+    val hide = _hide.asStateFlow()
+    private val _startingToHide = MutableStateFlow(false)
+    val startingToHide = _startingToHide.asStateFlow()
+    private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
+    val onDismissAction = _onDismissAction.asStateFlow()
+    private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
+    val startingDisappearAnimation = _disappearAnimation.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    val showMessage = _showMessage.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    /** Determines if user is already unlocked */
+    val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _onScreenTurnedOff = MutableStateFlow(false)
+    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+
+    val bouncerErrorMessage: CharSequence?
+        get() = viewMediatorCallback.consumeCustomMessage()
+
+    init {
+        val callback =
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onStrongAuthStateChanged(userId: Int) {
+                    bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
+                }
+
+                override fun onLockedOutStateChanged(type: BiometricSourceType) {
+                    if (type == BiometricSourceType.FINGERPRINT) {
+                        bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
+                    }
+                }
+            }
+
+        keyguardUpdateMonitor.registerCallback(callback)
+    }
+
+    fun setScrimmed(isScrimmed: Boolean) {
+        _isScrimmed.value = isScrimmed
+    }
+
+    fun setExpansion(expansion: Float) {
+        _expansionAmount.value = expansion
+    }
+
+    fun setVisible(isVisible: Boolean) {
+        _isVisible.value = isVisible
+    }
+
+    fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+        _show.value = keyguardBouncerModel
+    }
+
+    fun setShowingSoon(showingSoon: Boolean) {
+        _showingSoon.value = showingSoon
+    }
+
+    fun setHide(hide: Boolean) {
+        _hide.value = hide
+    }
+
+    fun setStartingToHide(startingToHide: Boolean) {
+        _startingToHide.value = startingToHide
+    }
+
+    fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
+        _onDismissAction.value = bouncerCallbackActionsModel
+    }
+
+    fun setStartDisappearAnimation(runnable: Runnable?) {
+        _disappearAnimation.value = runnable
+    }
+
+    fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+        _onScreenTurnedOff.value = onScreenTurnedOff
+    }
+}
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 840a4b2..45b668e 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
@@ -85,6 +85,15 @@
      */
     val dozeAmount: Flow<Float>
 
+    /**
+     * Returns `true` if the keyguard is showing; `false` otherwise.
+     *
+     * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+     * the z-order (which is not really above the system UI window, but rather - the lock-screen
+     * becomes invisible to reveal the "occluding activity").
+     */
+    fun isKeyguardShowing(): Boolean
+
     /** Sets whether the bottom area UI should animate the transition out of doze state. */
     fun setAnimateDozingTransitions(animate: Boolean)
 
@@ -103,7 +112,7 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    keyguardStateController: KeyguardStateController,
+    private val keyguardStateController: KeyguardStateController,
     dozeHost: DozeHost,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
@@ -148,7 +157,11 @@
                         }
                     }
                 dozeHost.addCallback(callback)
-                trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+                trySendWithFailureLogging(
+                    statusBarStateController.isDozing,
+                    TAG,
+                    "initial isDozing",
+                )
 
                 awaitClose { dozeHost.removeCallback(callback) }
             }
@@ -168,6 +181,10 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override fun isKeyguardShowing(): Boolean {
+        return keyguardStateController.isShowing
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
new file mode 100644
index 0000000..10c7a37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.statusbar.phone.KeyguardBouncer
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/** Interactor to add and remove callbacks for the bouncer. */
+@SysUISingleton
+class BouncerCallbackInteractor @Inject constructor() {
+    private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
+    private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
+    /** Add a KeyguardResetCallback. */
+    fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+        resetCallbacks.addIfAbsent(callback)
+    }
+
+    /** Remove a KeyguardResetCallback. */
+    fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+        resetCallbacks.remove(callback)
+    }
+
+    /** Adds a callback to listen to bouncer expansion updates. */
+    fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+        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: KeyguardBouncer.BouncerExpansionCallback) {
+        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()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
new file mode 100644
index 0000000..7d4db37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ColorStateList
+import android.os.Handler
+import android.os.Trace
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+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.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Encapsulates business logic for interacting with the lock-screen bouncer. */
+@SysUISingleton
+class BouncerInteractor
+@Inject
+constructor(
+    private val repository: KeyguardBouncerRepository,
+    private val bouncerView: BouncerView,
+    @Main private val mainHandler: Handler,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val callbackInteractor: BouncerCallbackInteractor,
+    private val falsingCollector: FalsingCollector,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    keyguardBypassController: KeyguardBypassController,
+    keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    /** Whether we want to wait for face auth. */
+    private val bouncerFaceDelay =
+        keyguardStateController.isFaceAuthEnabled &&
+            !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser()
+            ) &&
+            !needsFullscreenBouncer() &&
+            !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+            !keyguardBypassController.bypassEnabled
+
+    /** Runnable to show the bouncer. */
+    val showRunnable = Runnable {
+        repository.setVisible(true)
+        repository.setShow(
+            KeyguardBouncerModel(
+                promptReason = repository.bouncerPromptReason ?: 0,
+                errorMessage = repository.bouncerErrorMessage,
+                expansionAmount = repository.expansionAmount.value
+            )
+        )
+        repository.setShowingSoon(false)
+    }
+
+    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+    val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
+    val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
+    val hide: Flow<Unit> = repository.hide.filter { it }.map {}
+    val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
+    val isVisible: Flow<Boolean> = repository.isVisible
+    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
+    val expansionAmount: Flow<Float> = repository.expansionAmount
+    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
+    val startingDisappearAnimation: Flow<Runnable> =
+        repository.startingDisappearAnimation.filterNotNull()
+    val onDismissAction: Flow<BouncerCallbackActionsModel> =
+        repository.onDismissAction.filterNotNull()
+    val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
+    val keyguardPosition: Flow<Float> = repository.keyguardPosition
+
+    // 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.setShowMessage(null)
+        repository.setOnScreenTurnedOff(false)
+        repository.setKeyguardAuthenticated(null)
+        repository.setHide(false)
+        repository.setStartingToHide(false)
+
+        val resumeBouncer =
+            (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
+
+        if (!resumeBouncer && repository.show.value != null) {
+            // If bouncer is visible, the bouncer is already showing.
+            return
+        }
+
+        val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
+        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
+            // In split system user mode, we never unlock system user.
+            return
+        }
+
+        Trace.beginSection("KeyguardBouncer#show")
+        repository.setScrimmed(isScrimmed)
+        if (isScrimmed) {
+            setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+        }
+
+        if (resumeBouncer) {
+            bouncerView.delegate?.resume()
+            // Bouncer is showing the next security screen and we just need to prompt a resume.
+            return
+        }
+        if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+            // Keyguard is done.
+            return
+        }
+
+        repository.setShowingSoon(true)
+        if (bouncerFaceDelay) {
+            mainHandler.postDelayed(showRunnable, 1200L)
+        } else {
+            DejankUtils.postAfterTraversal(showRunnable)
+        }
+        keyguardStateController.notifyBouncerShowing(true)
+        callbackInteractor.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()
+        }
+
+        falsingCollector.onBouncerHidden()
+        keyguardStateController.notifyBouncerShowing(false /* showing */)
+        cancelShowRunnable()
+        repository.setShowingSoon(false)
+        repository.setOnDismissAction(null)
+        repository.setVisible(false)
+        repository.setHide(true)
+        repository.setShow(null)
+        Trace.endSection()
+    }
+
+    /**
+     * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
+     * where 0f => showing and 1f => hiding
+     */
+    fun setExpansion(expansion: Float) {
+        val oldExpansion = repository.expansionAmount.value
+        val expansionChanged = oldExpansion != expansion
+        if (repository.startingDisappearAnimation.value == null) {
+            repository.setExpansion(expansion)
+        }
+
+        if (
+            expansion == KeyguardBouncer.EXPANSION_VISIBLE &&
+                oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
+        ) {
+            falsingCollector.onBouncerShown()
+            callbackInteractor.dispatchFullyShown()
+        } else if (
+            expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
+                oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
+        ) {
+            repository.setVisible(false)
+            repository.setShow(null)
+            falsingCollector.onBouncerHidden()
+            DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
+            callbackInteractor.dispatchFullyHidden()
+        } else if (
+            expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
+                oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
+        ) {
+            callbackInteractor.dispatchStartingToHide()
+            repository.setStartingToHide(true)
+        }
+        if (expansionChanged) {
+            callbackInteractor.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?
+    ) {
+        repository.setOnDismissAction(BouncerCallbackActionsModel(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)
+    }
+
+    /** Tell the bouncer the screen has turned off. */
+    fun onScreenTurnedOff() {
+        repository.setOnScreenTurnedOff(true)
+    }
+
+    /** 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)
+    }
+
+    /** Notify that view visibility has changed. */
+    fun notifyBouncerVisibilityHasChanged(visibility: Int) {
+        callbackInteractor.dispatchVisibilityChanged(visibility)
+    }
+
+    /** 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) {
+        val finishRunnable = Runnable {
+            repository.setStartDisappearAnimation(null)
+            runnable.run()
+        }
+        repository.setStartDisappearAnimation(finishRunnable)
+    }
+
+    /** Returns whether bouncer is fully showing. */
+    fun isFullyShowing(): Boolean {
+        return (repository.showingSoon.value || repository.isVisible.value) &&
+            repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+            repository.startingDisappearAnimation.value == null
+    }
+
+    /** Returns whether bouncer is scrimmed. */
+    fun isScrimmed(): Boolean {
+        return repository.isScrimmed.value
+    }
+
+    /** If bouncer expansion is between 0f and 1f non-inclusive. */
+    fun isInTransit(): Boolean {
+        return repository.showingSoon.value ||
+            repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+                repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+    }
+
+    /** Return whether bouncer is animating away. */
+    fun isAnimatingAway(): Boolean {
+        return repository.startingDisappearAnimation.value != null
+    }
+
+    /** Return whether bouncer will dismiss with actions */
+    fun willDismissWithAction(): Boolean {
+        return repository.onDismissAction.value?.onDismissAction != null
+    }
+
+    /** 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)
+    }
+}
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 dccc941..192919e 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
@@ -29,7 +29,7 @@
 class KeyguardInteractor
 @Inject
 constructor(
-    repository: KeyguardRepository,
+    private val repository: KeyguardRepository,
 ) {
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -40,4 +40,8 @@
     val isDozing: Flow<Boolean> = repository.isDozing
     /** Whether the keyguard is showing ot not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+
+    fun isKeyguardShowing(): Boolean {
+        return repository.isKeyguardShowing()
+    }
 }
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 95acc0b..01cd3e2 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
@@ -104,7 +104,6 @@
                 KeyguardQuickAffordanceModel.Visible(
                     configKey = configs[index]::class,
                     icon = visibleState.icon,
-                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
                 )
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eff1469..eb6bb92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -17,8 +17,7 @@
 
 package com.android.systemui.keyguard.domain.model
 
-import androidx.annotation.StringRes
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
@@ -35,11 +34,6 @@
         /** Identifier for the affordance this is modeling. */
         val configKey: KClass<out KeyguardQuickAffordanceConfig>,
         /** An icon for the affordance. */
-        val icon: ContainedDrawable,
-        /**
-         * Resource ID for a string to use for the accessibility content description text of the
-         * affordance.
-         */
-        @StringRes val contentDescriptionResourceId: Int,
+        val icon: Icon,
     ) : KeyguardQuickAffordanceModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index ac2c9b1..89604f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.dagger.ControlsComponent
@@ -122,8 +123,14 @@
                 visibility == ControlsComponent.Visibility.AVAILABLE
         ) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithResource(iconResourceId),
-                contentDescriptionResourceId = component.getTileTitleId(),
+                icon =
+                    Icon.Resource(
+                        res = iconResourceId,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = component.getTileTitleId(),
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8fb952c..8e1c6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,9 +18,8 @@
 package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
-import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -44,12 +43,7 @@
         /** An affordance is visible. */
         data class Visible(
             /** An icon for the affordance. */
-            val icon: ContainedDrawable,
-            /**
-             * Resource ID for a string to use for the accessibility content description text of the
-             * affordance.
-             */
-            @StringRes val contentDescriptionResourceId: Int,
+            val icon: Icon,
         ) : State()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index c8e5e4a..d97deaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import javax.inject.Inject
@@ -76,8 +77,14 @@
     private fun state(): KeyguardQuickAffordanceConfig.State {
         return if (controller.isEnabledForLockScreenButton) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
-                contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+                icon =
+                    Icon.Resource(
+                        res = R.drawable.ic_qr_code_scanner,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_qr_code_scanner_button,
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 885af33..9196b09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -26,7 +26,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
@@ -100,8 +101,14 @@
     ): KeyguardQuickAffordanceConfig.State {
         return if (isFeatureEnabled && hasCard && tileIcon != null) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithDrawable(tileIcon),
-                contentDescriptionResourceId = R.string.accessibility_wallet_button,
+                icon =
+                    Icon.Loaded(
+                        drawable = tileIcon,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_wallet_button,
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
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
new file mode 100644
index 0000000..81cf5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/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.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/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
similarity index 67%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
index 104bf99..05cdeaa 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
@@ -11,18 +11,12 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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 android.service.cloudsearch;
+package com.android.systemui.keyguard.shared.model
 
-import android.app.cloudsearch.SearchRequest;
+import android.content.res.ColorStateList
 
-/**
- * Interface from the system to CloudSearch service.
- *
- * @hide
- */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+/** Show a keyguard message to the bouncer. */
+data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
similarity index 66%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
index 104bf99..ad783da 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
@@ -11,18 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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 android.service.cloudsearch;
+package com.android.systemui.keyguard.shared.model
 
-import android.app.cloudsearch.SearchRequest;
-
-/**
- * Interface from the system to CloudSearch service.
- *
- * @hide
- */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
-}
+/** Models the state of the lock-screen bouncer */
+data class KeyguardBouncerModel(
+    val promptReason: Int = 0,
+    val errorMessage: CharSequence? = null,
+    val expansionAmount: Float = 0f,
+)
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 c4e3d4e..65b85c0 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
@@ -31,7 +31,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -236,10 +236,7 @@
             }
         }
 
-        when (viewModel.icon) {
-            is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
-            is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
-        }
+        IconViewBinder.bind(viewModel.icon, view)
 
         view.drawable.setTint(
             Utils.getColorAttrDefaultColor(
@@ -250,7 +247,6 @@
         view.backgroundTintList =
             Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
 
-        view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             view.setOnClickListener(OnClickListener(viewModel, falsingManager))
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
new file mode 100644
index 0000000..df26014
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.policy.SystemBarUtils
+import com.android.keyguard.KeyguardHostViewController
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.collect
+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,
+        componentFactory: KeyguardBouncerComponent.Factory
+    ) {
+        // Builds the KeyguardHostViewController from bouncer view group.
+        val hostViewController: KeyguardHostViewController =
+            componentFactory.create(view).keyguardHostViewController
+        hostViewController.init()
+        val delegate =
+            object : BouncerViewDelegate {
+                override fun isFullScreenBouncer(): Boolean {
+                    val mode = hostViewController.currentSecurityMode
+                    return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+                        mode == KeyguardSecurityModel.SecurityMode.SimPuk
+                }
+
+                override fun shouldDismissOnMenuPressed(): Boolean {
+                    return hostViewController.shouldEnableMenuKey()
+                }
+
+                override fun interceptMediaKey(event: KeyEvent?): Boolean {
+                    return hostViewController.interceptMediaKey(event)
+                }
+
+                override fun dispatchBackKeyEventPreIme(): Boolean {
+                    return hostViewController.dispatchBackKeyEventPreIme()
+                }
+
+                override fun showNextSecurityScreenOrFinish(): Boolean {
+                    return hostViewController.dismiss(KeyguardUpdateMonitor.getCurrentUser())
+                }
+
+                override fun resume() {
+                    hostViewController.showPrimarySecurityScreen()
+                    hostViewController.onResume()
+                }
+            }
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                try {
+                    viewModel.setBouncerViewDelegate(delegate)
+                    launch {
+                        viewModel.show.collect {
+                            hostViewController.showPrimarySecurityScreen()
+                            hostViewController.appear(
+                                SystemBarUtils.getStatusBarHeight(view.context)
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.showPromptReason.collect { prompt ->
+                            hostViewController.showPromptReason(prompt)
+                        }
+                    }
+
+                    launch {
+                        viewModel.showBouncerErrorMessage.collect { errorMessage ->
+                            hostViewController.showErrorMessage(errorMessage)
+                        }
+                    }
+
+                    launch {
+                        viewModel.showWithFullExpansion.collect { model ->
+                            hostViewController.resetSecurityContainer()
+                            hostViewController.showPromptReason(model.promptReason)
+                            hostViewController.onResume()
+                        }
+                    }
+
+                    launch {
+                        viewModel.hide.collect {
+                            hostViewController.cancelDismissAction()
+                            hostViewController.cleanUp()
+                            hostViewController.resetSecurityContainer()
+                        }
+                    }
+
+                    launch {
+                        viewModel.startingToHide.collect { hostViewController.onStartingToHide() }
+                    }
+
+                    launch {
+                        viewModel.setDismissAction.collect {
+                            hostViewController.setOnDismissAction(
+                                it.onDismissAction,
+                                it.cancelAction
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.startDisappearAnimation.collect {
+                            hostViewController.startDisappearAnimation(it)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount.collect { expansion ->
+                            hostViewController.setExpansion(expansion)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount
+                            .filter { it == EXPANSION_VISIBLE }
+                            .collect {
+                                hostViewController.onResume()
+                                view.announceForAccessibility(
+                                    hostViewController.accessibilityTitleForCurrentMode
+                                )
+                            }
+                    }
+
+                    launch {
+                        viewModel.isBouncerVisible.collect { isVisible ->
+                            val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                            view.visibility = visibility
+                            hostViewController.onBouncerVisibilityChanged(visibility)
+                            viewModel.notifyBouncerVisibilityHasChanged(visibility)
+                        }
+                    }
+
+                    launch {
+                        viewModel.isBouncerVisible
+                            .filter { !it }
+                            .collect {
+                                // Remove existing input for security reasons.
+                                hostViewController.resetSecurityContainer()
+                            }
+                    }
+
+                    launch {
+                        viewModel.keyguardPosition.collect { position ->
+                            hostViewController.updateKeyguardPosition(position)
+                        }
+                    }
+
+                    launch {
+                        viewModel.updateResources.collect {
+                            hostViewController.updateResources()
+                            viewModel.notifyUpdateResources()
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerShowMessage.collect {
+                            hostViewController.showMessage(it.message, it.colorStateList)
+                        }
+                    }
+
+                    launch {
+                        viewModel.keyguardAuthenticated.collect {
+                            hostViewController.finish(it, KeyguardUpdateMonitor.getCurrentUser())
+                            viewModel.notifyKeyguardAuthenticated()
+                        }
+                    }
+
+                    launch {
+                        viewModel
+                            .observeOnIsBackButtonEnabled { view.systemUiVisibility }
+                            .collect { view.systemUiVisibility = it }
+                    }
+
+                    launch {
+                        viewModel.screenTurnedOff.collect {
+                            if (view.visibility == View.VISIBLE) {
+                                hostViewController.onPause()
+                            }
+                        }
+                    }
+                    awaitCancellation()
+                } finally {
+                    viewModel.setBouncerViewDelegate(null)
+                }
+            }
+        }
+    }
+}
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 e3ebac6..970ee4c 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
@@ -116,7 +116,6 @@
                     isVisible = true,
                     animateReveal = animateReveal,
                     icon = icon,
-                    contentDescriptionResourceId = contentDescriptionResourceId,
                     onClicked = { parameters ->
                         quickAffordanceInteractor.onQuickAffordanceClicked(
                             configKey = parameters.configKey,
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
new file mode 100644
index 0000000..9ad5211
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -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.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.BouncerInteractor
+import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Models UI state for the lock screen bouncer; handles user input. */
+class KeyguardBouncerViewModel
+@Inject
+constructor(
+    private val view: BouncerView,
+    private val interactor: BouncerInteractor,
+) {
+    /** Observe on bouncer expansion amount. */
+    val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+
+    /** Observe on bouncer visibility. */
+    val isBouncerVisible: Flow<Boolean> = interactor.isVisible
+
+    /** Observe whether bouncer is showing. */
+    val show: Flow<KeyguardBouncerModel> = interactor.show
+
+    /** Observe bouncer prompt when bouncer is showing. */
+    val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
+
+    /** Observe bouncer error message when bouncer is showing. */
+    val showBouncerErrorMessage: Flow<CharSequence> =
+        interactor.show.map { it.errorMessage }.filterNotNull()
+
+    /** Observe visible expansion when bouncer is showing. */
+    val showWithFullExpansion: Flow<KeyguardBouncerModel> =
+        interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
+
+    /** Observe whether bouncer is hiding. */
+    val hide: Flow<Unit> = interactor.hide
+
+    /** Observe whether bouncer is starting to hide. */
+    val startingToHide: Flow<Unit> = interactor.startingToHide
+
+    /** Observe whether we want to set the dismiss action to the bouncer. */
+    val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
+
+    /** 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 screen is turned off. */
+    val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
+
+    /** Notify that view visibility has changed. */
+    fun notifyBouncerVisibilityHasChanged(visibility: Int) {
+        return interactor.notifyBouncerVisibilityHasChanged(visibility)
+    }
+    /** Observe whether we want to update resources. */
+    fun notifyUpdateResources() {
+        interactor.notifyUpdatedResources()
+    }
+
+    /** Notify that keyguard authenticated was handled */
+    fun notifyKeyguardAuthenticated() {
+        interactor.notifyKeyguardAuthenticatedHandled()
+    }
+
+    /** 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/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index b1de27d..0971f13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,9 +16,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
@@ -28,8 +27,7 @@
     val isVisible: Boolean = false,
     /** Whether to animate the transition of the quick affordance from invisible to visible. */
     val animateReveal: Boolean = false,
-    val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
-    @StringRes val contentDescriptionResourceId: Int = 0,
+    val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
     val onClicked: (OnClickedParameters) -> Unit = {},
     val isClickable: Boolean = false,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
new file mode 100644
index 0000000..aef3471
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A [com.android.systemui.log.LogBuffer] for keyguard-related stuff. Should be used mostly for
+ * adding temporary logs or logging from smaller classes when creating new separate log class might
+ * be an overkill.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class KeyguardLog
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 29e2c1c..0c5564b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -43,7 +43,7 @@
     @SysUISingleton
     @DozeLog
     public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
-        return factory.create("DozeLog", 100);
+        return factory.create("DozeLog", 120);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -334,4 +334,24 @@
     public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
         return factory.create("BluetoothLog", 50);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for Udfps logs.
+     */
+    @Provides
+    @SysUISingleton
+    @UdfpsLog
+    public static LogBuffer provideUdfpsLogBuffer(LogBufferFactory factory) {
+        return factory.create("UdfpsLog", 1000);
+    }
+
+    /**
+     * Provides a {@link LogBuffer} for general keyguard-related logs.
+     */
+    @Provides
+    @SysUISingleton
+    @KeyguardLog
+    public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyguardLog", 250);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
similarity index 67%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
index 9b370d8..14000e1 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package com.android.systemui.log.dagger;
 
-/**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
- */
-public final class SoftwareCursorManager {
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface UdfpsLog {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b36f33b..2cd564f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.media
 
+import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import android.content.res.ColorStateList
@@ -148,32 +149,6 @@
                 }
             }
         }
-
-    companion object {
-        private const val SQUISHINESS_SCALE_START = 0.5
-        private const val SQUISHINESS_SCALE_FACTOR = 0.5
-        private fun getSquishinessScale(squishinessFraction: Float): Double {
-            return SQUISHINESS_SCALE_START + SQUISHINESS_SCALE_FACTOR * squishinessFraction
-        }
-    }
-
-    var squishinessFraction: Float = 1f
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-
-            val scale = getSquishinessScale(field)
-            for (mediaPlayer in MediaPlayerData.players()) {
-                mediaPlayer.mediaViewHolder?.let {
-                    it.player.bottom = it.player.top + (scale * it.player.measuredHeight).toInt()
-                } ?: mediaPlayer.recommendationViewHolder?.let {
-                    it.recommendations.bottom = it.recommendations.top +
-                            (scale * it.recommendations.measuredHeight).toInt()
-                }
-            }
-        }
     private val configListener = object : ConfigurationController.ConfigurationListener {
         override fun onDensityOrFontScaleChanged() {
             // System font changes should only happen when UMO is offscreen or a flicker may occur
@@ -945,6 +920,11 @@
         mediaManager.onSwipeToDismiss()
     }
 
+    fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
+        return MediaPlayerData.playerKeys()
+                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.apply {
             println("keysNeedRemoval: $keysNeedRemoval")
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 8645922..bffb0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -203,14 +203,6 @@
                 }
             }
 
-        override var squishFraction: Float = 1.0f
-            set(value) {
-                if (!value.equals(field)) {
-                    field = value
-                    changedListener?.invoke()
-                }
-            }
-
         override var showsOnlyActiveMedia: Boolean = false
             set(value) {
                 if (!value.equals(field)) {
@@ -261,7 +253,6 @@
         override fun copy(): MediaHostState {
             val mediaHostState = MediaHostStateHolder()
             mediaHostState.expansion = expansion
-            mediaHostState.squishFraction = squishFraction
             mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
             mediaHostState.measurementInput = measurementInput?.copy()
             mediaHostState.visible = visible
@@ -280,9 +271,6 @@
             if (expansion != other.expansion) {
                 return false
             }
-            if (squishFraction != other.squishFraction) {
-                return false
-            }
             if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
                 return false
             }
@@ -301,7 +289,6 @@
         override fun hashCode(): Int {
             var result = measurementInput?.hashCode() ?: 0
             result = 31 * result + expansion.hashCode()
-            result = 31 * result + squishFraction.hashCode()
             result = 31 * result + falsingProtectionNeeded.hashCode()
             result = 31 * result + showsOnlyActiveMedia.hashCode()
             result = 31 * result + if (visible) 1 else 2
@@ -342,11 +329,6 @@
     var expansion: Float
 
     /**
-     * Fraction of the height animation.
-     */
-    var squishFraction: Float
-
-    /**
      * Is this host only showing active media or is it showing all of them including resumption?
      */
     var showsOnlyActiveMedia: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 0f1ee31..c6bd777 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -24,22 +24,45 @@
 import android.os.IBinder
 import android.os.ResultReceiver
 import android.os.UserHandle
-import android.widget.ImageView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.app.ChooserActivity
 import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
 import com.android.internal.app.chooser.TargetInfo
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.util.AsyncActivityLauncher
 import com.android.systemui.R
-import com.android.internal.R as AndroidR
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.util.AsyncActivityLauncher
+import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import javax.inject.Inject
 
-class MediaProjectionAppSelectorActivity constructor(
+class MediaProjectionAppSelectorActivity(
     private val activityLauncher: AsyncActivityLauncher,
+    private val controller: MediaProjectionAppSelectorController,
+    private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
-    private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null
-) : ChooserActivity() {
+    private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
+) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener {
+
+    @Inject
+    constructor(
+        activityLauncher: AsyncActivityLauncher,
+        controller: MediaProjectionAppSelectorController,
+        recentTasksAdapterFactory: RecentTasksAdapter.Factory,
+    ) : this(activityLauncher, controller, recentTasksAdapterFactory, null)
+
+    private var recentsRoot: ViewGroup? = null
+    private var recentsProgress: View? = null
+    private var recentsRecycler: RecyclerView? = null
 
     override fun getLayoutResource() =
         R.layout.media_projection_app_selector
@@ -52,10 +75,30 @@
         // TODO(b/240939253): update copies
         val title = getString(R.string.media_projection_dialog_service_title)
         intent.putExtra(Intent.EXTRA_TITLE, title)
-
         super.onCreate(bundle)
+        controller.init(this)
+    }
 
-        requireViewById<ImageView>(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all)
+    private fun createRecentsView(parent: ViewGroup): ViewGroup {
+        val recentsRoot = LayoutInflater.from(this)
+            .inflate(R.layout.media_projection_recent_tasks, parent,
+                    /* attachToRoot= */ false) as ViewGroup
+
+        recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader)
+        recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler)
+        recentsRecycler?.layoutManager = LinearLayoutManager(
+            this, LinearLayoutManager.HORIZONTAL,
+            /* reverseLayout= */false
+        )
+
+        val itemDecoration = HorizontalSpacerItemDecoration(
+            resources.getDimensionPixelOffset(
+                R.dimen.media_projection_app_selector_recents_padding
+            )
+        )
+        recentsRecycler?.addItemDecoration(itemDecoration)
+
+        return recentsRoot
     }
 
     override fun appliedThemeResId(): Int =
@@ -108,6 +151,7 @@
 
     override fun onDestroy() {
         activityLauncher.destroy()
+        controller.destroy()
         super.onDestroy()
     }
 
@@ -115,6 +159,27 @@
         // do nothing
     }
 
+    override fun bind(recentTasks: List<RecentTask>) {
+        val recents = recentsRoot ?: return
+        val progress = recentsProgress ?: return
+        val recycler = recentsRecycler ?: return
+
+        if (recentTasks.isEmpty()) {
+            recents.visibility = View.GONE
+            return
+        }
+
+        progress.visibility = View.GONE
+        recycler.visibility = View.VISIBLE
+        recents.visibility = View.VISIBLE
+
+        recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this)
+    }
+
+    override fun onRecentClicked(task: RecentTask, view: View) {
+        // TODO(b/240924732) Handle clicking on a recent task
+    }
+
     private fun onTargetActivityLaunched(launchToken: IBinder) {
         if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
             // The client requested to return the result in the result receiver instead of
@@ -145,6 +210,14 @@
 
     override fun shouldGetOnlyDefaultActivities() = false
 
+    // TODO(b/240924732) flip the flag when the recents selector is ready
+    override fun shouldShowContentPreview() = false
+
+    override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
+            recentsRoot ?: createRecentsView(parent).also {
+                recentsRoot = it
+            }
+
     companion object {
         /**
          * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 731e348..ac59175 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.Configuration
-import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.R
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -280,79 +279,53 @@
     }
 
     /**
-     * Apply squishFraction to a copy of viewState such that the cached version is untouched.
-     */
-    @VisibleForTesting
-    internal fun squishViewState(
-        viewState: TransitionViewState,
-        squishFraction: Float
-    ): TransitionViewState {
-        val squishedViewState = viewState.copy()
-        squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
-        val albumArtViewState = squishedViewState.widgetStates.get(R.id.album_art)
-        if (albumArtViewState != null) {
-            albumArtViewState.height = squishedViewState.height
-        }
-        return squishedViewState
-    }
-
-    /**
      * Obtain a new viewState for a given media state. This usually returns a cached state, but if
      * it's not available, it will recreate one by measuring, which may be expensive.
      */
-    @VisibleForTesting
-    public fun obtainViewState(state: MediaHostState?): TransitionViewState? {
+    private fun obtainViewState(state: MediaHostState?): TransitionViewState? {
         if (state == null || state.measurementInput == null) {
             return null
         }
         // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
         var cacheKey = getKey(state, isGutsVisible, tmpKey)
         val viewState = viewStates[cacheKey]
-
         if (viewState != null) {
             // we already have cached this measurement, let's continue
-            if (state.squishFraction < 1f) {
-                return squishViewState(viewState, state.squishFraction)
-            }
             return viewState
         }
         // Copy the key since this might call recursively into it and we're using tmpKey
         cacheKey = cacheKey.copy()
         val result: TransitionViewState?
-        if (transitionLayout == null) {
-            return null
-        }
 
-        // Not cached. Let's create a new measurement
-        if (state.expansion == 0.0f || state.expansion == 1.0f) {
-            result = transitionLayout!!.calculateViewState(
-                    state.measurementInput!!,
-                    constraintSetForExpansion(state.expansion),
-                    TransitionViewState())
-            // We don't want to cache interpolated or null states as this could quickly fill up
-            // our cache. We only cache the start and the end states since the interpolation
-            // is cheap
-            setGutsViewState(result)
-            viewStates[cacheKey] = result
-            logger.logMediaSize("measured new viewState", result.width, result.height)
+        if (transitionLayout != null) {
+            // Let's create a new measurement
+            if (state.expansion == 0.0f || state.expansion == 1.0f) {
+                result = transitionLayout!!.calculateViewState(
+                        state.measurementInput!!,
+                        constraintSetForExpansion(state.expansion),
+                        TransitionViewState())
+
+                setGutsViewState(result)
+                // We don't want to cache interpolated or null states as this could quickly fill up
+                // our cache. We only cache the start and the end states since the interpolation
+                // is cheap
+                viewStates[cacheKey] = result
+            } else {
+                // This is an interpolated state
+                val startState = state.copy().also { it.expansion = 0.0f }
+
+                // Given that we have a measurement and a view, let's get (guaranteed) viewstates
+                // from the start and end state and interpolate them
+                val startViewState = obtainViewState(startState) as TransitionViewState
+                val endState = state.copy().also { it.expansion = 1.0f }
+                val endViewState = obtainViewState(endState) as TransitionViewState
+                result = layoutController.getInterpolatedState(
+                        startViewState,
+                        endViewState,
+                        state.expansion)
+            }
         } else {
-            // This is an interpolated state
-            val startState = state.copy().also { it.expansion = 0.0f }
-
-            // Given that we have a measurement and a view, let's get (guaranteed) viewstates
-            // from the start and end state and interpolate them
-            val startViewState = obtainViewState(startState) as TransitionViewState
-            val endState = state.copy().also { it.expansion = 1.0f }
-
-            val endViewState = obtainViewState(endState) as TransitionViewState
-            result = layoutController.getInterpolatedState(
-                    startViewState,
-                    endViewState,
-                    state.expansion)
-            logger.logMediaSize("interpolated viewState", result.width, result.height)
-        }
-        if (state.squishFraction < 1f) {
-            return squishViewState(result, state.squishFraction)
+            result = null
         }
         return result
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 0359c63..17ebfec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -30,7 +30,10 @@
 import androidx.core.view.GestureDetectorCompat
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
+import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.util.concurrency.RepeatableExecutor
 import javax.inject.Inject
@@ -72,7 +75,8 @@
 
 /** ViewModel for seek bar in QS media player. */
 class SeekBarViewModel @Inject constructor(
-    @Background private val bgExecutor: RepeatableExecutor
+    @Background private val bgExecutor: RepeatableExecutor,
+    private val falsingManager: FalsingManager,
 ) {
     private var _data = Progress(false, false, false, false, null, 0)
         set(value) {
@@ -275,7 +279,7 @@
     /** Gets a listener to attach to the seek bar to handle seeking. */
     val seekBarListener: SeekBar.OnSeekBarChangeListener
         get() {
-            return SeekBarChangeListener(this)
+            return SeekBarChangeListener(this, falsingManager)
         }
 
     /** Attach touch handlers to the seek bar view. */
@@ -315,7 +319,8 @@
     }
 
     private class SeekBarChangeListener(
-        val viewModel: SeekBarViewModel
+        val viewModel: SeekBarViewModel,
+        val falsingManager: FalsingManager,
     ) : SeekBar.OnSeekBarChangeListener {
         override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
             if (fromUser) {
@@ -328,6 +333,13 @@
         }
 
         override fun onStopTrackingTouch(bar: SeekBar) {
+            // in addition to the normal functionality of both functions.
+            // isFalseTouch returns true if there is a real/false tap since it is not a move.
+            // isFalseTap returns true if there is a real/false move since it is not a tap.
+            if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
+                    falsingManager.isFalseTap(LOW_PENALTY)) {
+                viewModel.onSeekFalse()
+            }
             viewModel.onSeek(bar.progress.toLong())
         }
     }
@@ -340,7 +352,7 @@
      */
     private class SeekBarTouchListener(
         private val viewModel: SeekBarViewModel,
-        private val bar: SeekBar
+        private val bar: SeekBar,
     ) : View.OnTouchListener, GestureDetector.OnGestureListener {
 
         // Gesture detector helps decide which touch events to intercept.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
index 9696998..185b4fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
@@ -17,13 +17,29 @@
 package com.android.systemui.media.dagger
 
 import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.util.AsyncActivityLauncher
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class MediaProjectionAppSelector
 
 @Module
 abstract class MediaProjectionModule {
@@ -31,17 +47,43 @@
     @Binds
     @IntoMap
     @ClassKey(MediaProjectionAppSelectorActivity::class)
-    abstract fun bindMediaProjectionAppSelectorActivity(
-        activity: MediaProjectionAppSelectorActivity): Activity
+    abstract fun provideMediaProjectionAppSelectorActivity(
+        activity: MediaProjectionAppSelectorActivity
+    ): Activity
+
+    @Binds
+    abstract fun bindRecentTaskThumbnailLoader(
+        impl: ActivityTaskManagerThumbnailLoader
+    ): RecentTaskThumbnailLoader
+
+    @Binds
+    abstract fun bindRecentTaskListProvider(
+        impl: ShellRecentTaskListProvider
+    ): RecentTaskListProvider
+
+    @Binds
+    abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
 
     companion object {
         @Provides
-        fun provideMediaProjectionAppSelectorActivity(
-            activityLauncher: AsyncActivityLauncher
-        ): MediaProjectionAppSelectorActivity {
-            return MediaProjectionAppSelectorActivity(
-                activityLauncher
+        fun provideController(
+            recentTaskListProvider: RecentTaskListProvider,
+            context: Context,
+            @MediaProjectionAppSelector scope: CoroutineScope
+        ): MediaProjectionAppSelectorController {
+            val appSelectorComponentName =
+                ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+
+            return MediaProjectionAppSelectorController(
+                recentTaskListProvider,
+                scope,
+                appSelectorComponentName
             )
         }
+
+        @MediaProjectionAppSelector
+        @Provides
+        fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 2d09ddd..a9e1a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -99,6 +99,7 @@
     private Button mStopButton;
     private Button mAppButton;
     private int mListMaxHeight;
+    private int mItemHeight;
     private WallpaperColors mWallpaperColors;
     private Executor mExecutor;
     private boolean mShouldLaunchLeBroadcastDialog;
@@ -106,10 +107,12 @@
     MediaOutputBaseAdapter mAdapter;
 
     private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
+        ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
+        int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
+        int correctHeight = Math.min(totalItemsHeight, mListMaxHeight);
         // Set max height for list
-        if (mDeviceListLayout.getHeight() > mListMaxHeight) {
-            ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
-            params.height = mListMaxHeight;
+        if (correctHeight != params.height) {
+            params.height = correctHeight;
             mDeviceListLayout.setLayoutParams(params);
         }
     };
@@ -212,6 +215,8 @@
         mLayoutManager = new LayoutManagerWrapper(mContext);
         mListMaxHeight = context.getResources().getDimensionPixelSize(
                 R.dimen.media_output_dialog_list_max_height);
+        mItemHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.media_output_dialog_list_item_height);
         mExecutor = Executors.newSingleThreadExecutor();
     }
 
@@ -246,8 +251,10 @@
         mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
                 mDeviceListLayoutListener);
         // Init device list
+        mLayoutManager.setAutoMeasureEnabled(true);
         mDevicesRecyclerView.setLayoutManager(mLayoutManager);
         mDevicesRecyclerView.setAdapter(mAdapter);
+        mDevicesRecyclerView.setHasFixedSize(false);
         // Init header icon
         mHeaderIcon.setOnClickListener(v -> onHeaderIconClick());
         // Init bottom buttons
@@ -509,7 +516,7 @@
     abstract int getStopButtonVisibility();
 
     public CharSequence getStopButtonText() {
-        return mContext.getText(R.string.media_output_dialog_button_stop_casting);
+        return mContext.getText(R.string.keyboard_key_media_stop);
     }
 
     public void onStopButtonClick() {
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 f040e06..19b401d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -125,13 +125,16 @@
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
     private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
 
-    private boolean mIsRefreshing = false;
-    private boolean mNeedRefresh = false;
+    @VisibleForTesting
+    boolean mIsRefreshing = false;
+    @VisibleForTesting
+    boolean mNeedRefresh = false;
     private MediaController mMediaController;
     @VisibleForTesting
     Callback mCallback;
     @VisibleForTesting
     LocalMediaManager mLocalMediaManager;
+    @VisibleForTesting
     private MediaOutputMetricLogger mMetricLogger;
     private int mCurrentState;
 
@@ -995,7 +998,7 @@
                 return;
             }
 
-            if (newState == PlaybackState.STATE_STOPPED || newState == PlaybackState.STATE_PAUSED) {
+            if (newState == PlaybackState.STATE_STOPPED) {
                 mCallback.onMediaStoppedOrPaused();
             }
             mCurrentState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index cb6f5a7..fbd0079 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -108,7 +108,7 @@
 
     @Override
     public CharSequence getStopButtonText() {
-        int resId = R.string.media_output_dialog_button_stop_casting;
+        int resId = R.string.keyboard_key_media_stop;
         if (isBroadcastSupported() && mMediaOutputController.isPlaying()
                 && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
             resId = R.string.media_output_broadcast;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index dc1488e..53b4d43 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.media.dream;
 
-import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION;
 
 import android.content.Context;
 import android.util.Log;
@@ -77,7 +77,7 @@
         public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
                 @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency,
                 boolean isSsReactivated) {
-            if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) {
+            if (!mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)) {
                 return;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 4379d25..aae973d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -25,6 +25,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
 
 /**
@@ -107,12 +108,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
                 )
@@ -143,12 +147,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
                 )
@@ -215,7 +222,8 @@
         controllerSender: MediaTttChipControllerSender,
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?,
-        uiEventLogger: MediaTttSenderUiEventLogger
+        uiEventLogger: MediaTttSenderUiEventLogger,
+        falsingManager: FalsingManager,
     ): View.OnClickListener? = null
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index e539f3f..007eb8f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -22,25 +22,30 @@
 import android.os.PowerManager
 import android.util.Log
 import android.view.Gravity
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.TextView
 import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -57,7 +62,11 @@
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
-        private val uiEventLogger: MediaTttSenderUiEventLogger
+        private val uiEventLogger: MediaTttSenderUiEventLogger,
+        // Added Lazy<> to delay the time we create Falsing instances.
+        // And overcome performance issue, check [b/247817628] for details.
+        private val falsingManager: Lazy<FalsingManager>,
+        private val falsingCollector: Lazy<FalsingCollector>,
 ) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
         context,
         logger,
@@ -70,6 +79,9 @@
         MediaTttUtils.WINDOW_TITLE,
         MediaTttUtils.WAKE_REASON,
 ) {
+
+    private lateinit var parent: MediaTttChipRootView
+
     override val windowLayoutParams = commonWindowLayoutParams.apply {
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
@@ -120,6 +132,15 @@
 
         val chipState = newInfo.state
 
+        // Detect falsing touches on the chip.
+        parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
+        parent.touchHandler = object : Gefingerpoken {
+            override fun onTouchEvent(ev: MotionEvent?): Boolean {
+                falsingCollector.get().onTouchEvent(ev)
+                return false
+            }
+        }
+
         // App icon
         val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context, newInfo.routeInfo.clientPackageName, logger
@@ -142,7 +163,11 @@
         // Undo
         val undoView = currentView.requireViewById<View>(R.id.undo)
         val undoClickListener = chipState.undoClickListener(
-                this, newInfo.routeInfo, newInfo.undoCallback, uiEventLogger
+                this,
+                newInfo.routeInfo,
+                newInfo.undoCallback,
+                uiEventLogger,
+                falsingManager.get(),
         )
         undoView.setOnClickListener(undoClickListener)
         undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
new file mode 100644
index 0000000..3373159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events on chip. */
+class MediaTttChipRootView(
+        context: Context,
+        attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+    /** Assign this field to observe touch events. */
+    var touchHandler: Gefingerpoken? = null
+
+    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+        touchHandler?.onTouchEvent(ev)
+        return super.dispatchTouchEvent(ev)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
new file mode 100644
index 0000000..2b381a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.appselector
+
+import android.content.ComponentName
+import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+
+class MediaProjectionAppSelectorController(
+    private val recentTaskListProvider: RecentTaskListProvider,
+    @MediaProjectionAppSelector private val scope: CoroutineScope,
+    private val appSelectorComponentName: ComponentName
+) {
+
+    fun init(view: MediaProjectionAppSelectorView) {
+        scope.launch {
+            val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
+            view.bind(tasks)
+        }
+    }
+
+    fun destroy() {
+        scope.cancel()
+    }
+
+    private fun List<RecentTask>.sortTasks(): List<RecentTask> =
+        sortedBy {
+            // Show normal tasks first and only then tasks with opened app selector
+            it.topActivityComponent == appSelectorComponentName
+        }
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorView.kt
similarity index 71%
copy from core/java/android/service/cloudsearch/ICloudSearchService.aidl
copy to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorView.kt
index 104bf99..6550aa5 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorView.kt
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-package android.service.cloudsearch;
+package com.android.systemui.mediaprojection.appselector
 
-import android.app.cloudsearch.SearchRequest;
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
 
-/**
- * Interface from the system to CloudSearch service.
- *
- * @hide
- */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
+interface MediaProjectionAppSelectorView {
+    fun bind(recentTasks: List<RecentTask>)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
new file mode 100644
index 0000000..0927f3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.appselector.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.IconFactory
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface AppIconLoader {
+    suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
+}
+
+class IconLoaderLibAppIconLoader
+@Inject
+constructor(
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val context: Context,
+    private val packageManager: PackageManager
+) : AppIconLoader {
+
+    override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
+        withContext(backgroundDispatcher) {
+            IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
+                val activityInfo = packageManager
+                        .getActivityInfo(component, ComponentInfoFlags.of(0))
+                val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
+                val userHandler = UserHandle.of(userId)
+                val options = IconOptions().apply { setUser(userHandler) }
+                val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+                badgedIcon.newIcon(context)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
copy to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 44c0496..cd994b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.mediaprojection.appselector.data
 
-/**
- * Provides information on the current wifi activity.
- */
-data class WifiActivityModel(
-    /** True if the wifi has activity in (download). */
-    val hasActivityIn: Boolean,
-    /** True if the wifi has activity out (upload). */
-    val hasActivityOut: Boolean,
+import android.annotation.ColorInt
+import android.content.ComponentName
+
+data class RecentTask(
+    val taskId: Int,
+    val userId: Int,
+    val topActivityComponent: ComponentName?,
+    val baseIntentComponent: ComponentName?,
+    @ColorInt val colorBackground: Int?
 )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
new file mode 100644
index 0000000..e8b49cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.appselector.data
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+import java.util.concurrent.Executor
+
+interface RecentTaskListProvider {
+    /** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
+    suspend fun loadRecentTasks(): List<RecentTask>
+}
+
+class ShellRecentTaskListProvider
+@Inject
+constructor(
+    @Background private val coroutineDispatcher: CoroutineDispatcher,
+    @Background private val backgroundExecutor: Executor,
+    private val recentTasks: Optional<RecentTasks>
+) : RecentTaskListProvider {
+
+    private val recents by lazy { recentTasks.getOrNull() }
+
+    override suspend fun loadRecentTasks(): List<RecentTask> =
+        withContext(coroutineDispatcher) {
+            val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+
+            rawRecentTasks
+                .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
+                .map {
+                    RecentTask(
+                        it.taskId,
+                        it.userId,
+                        it.topActivity,
+                        it.baseIntent?.component,
+                        it.taskDescription?.backgroundColor
+                    )
+                }
+        }
+
+    private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
+        suspendCoroutine { continuation ->
+            getRecentTasks(
+                Integer.MAX_VALUE,
+                RECENT_IGNORE_UNAVAILABLE,
+                ActivityManager.getCurrentUser(),
+                backgroundExecutor
+            ) { tasks ->
+                continuation.resume(tasks)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
new file mode 100644
index 0000000..47faaed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.appselector.data
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface RecentTaskThumbnailLoader {
+    suspend fun loadThumbnail(taskId: Int): ThumbnailData?
+}
+
+class ActivityTaskManagerThumbnailLoader
+@Inject
+constructor(
+    @Background private val coroutineDispatcher: CoroutineDispatcher,
+    private val activityManager: ActivityManagerWrapper
+) : RecentTaskThumbnailLoader {
+
+    override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
+        withContext(coroutineDispatcher) {
+            val thumbnailData =
+                activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
+            if (thumbnailData.thumbnail == null) null else thumbnailData
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
new file mode 100644
index 0000000..ec5abc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+class RecentTaskViewHolder @AssistedInject constructor(
+    @Assisted root: ViewGroup,
+    private val iconLoader: AppIconLoader,
+    private val thumbnailLoader: RecentTaskThumbnailLoader,
+    @MediaProjectionAppSelector private val scope: CoroutineScope
+) : RecyclerView.ViewHolder(root) {
+
+    private val iconView: ImageView = root.requireViewById(R.id.task_icon)
+    private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail)
+
+    private var job: Job? = null
+
+    fun bind(task: RecentTask, onClick: (View) -> Unit) {
+        job?.cancel()
+
+        job =
+            scope.launch {
+                task.baseIntentComponent?.let { component ->
+                    launch {
+                        val icon = iconLoader.loadIcon(task.userId, component)
+                        iconView.setImageDrawable(icon)
+                    }
+                }
+                launch {
+                    val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
+                    thumbnailView.setImageBitmap(thumbnail?.thumbnail)
+                }
+            }
+
+        thumbnailView.setOnClickListener(onClick)
+    }
+
+    fun onRecycled() {
+        iconView.setImageDrawable(null)
+        thumbnailView.setImageBitmap(null)
+        job?.cancel()
+        job = null
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(root: ViewGroup): RecentTaskViewHolder
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
new file mode 100644
index 0000000..ec9cfa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.appselector.view
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class RecentTasksAdapter @AssistedInject constructor(
+    @Assisted private val items: List<RecentTask>,
+    @Assisted private val listener: RecentTaskClickListener,
+    private val viewHolderFactory: RecentTaskViewHolder.Factory
+) : RecyclerView.Adapter<RecentTaskViewHolder>() {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder {
+        val taskItem =
+            LayoutInflater.from(parent.context)
+                .inflate(R.layout.media_projection_task_item, null) as ViewGroup
+
+        return viewHolderFactory.create(taskItem)
+    }
+
+    override fun onBindViewHolder(holder: RecentTaskViewHolder, position: Int) {
+        val task = items[position]
+        holder.bind(task, onClick = {
+            listener.onRecentClicked(task, holder.itemView)
+        })
+    }
+
+    override fun getItemCount(): Int = items.size
+
+    override fun onViewRecycled(holder: RecentTaskViewHolder) {
+        holder.onRecycled()
+    }
+
+    interface RecentTaskClickListener {
+        fun onRecentClicked(task: RecentTask, view: View)
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(items: List<RecentTask>, listener: RecentTaskClickListener): RecentTasksAdapter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 30947e8..50a10bc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -541,10 +541,12 @@
                 if (!mImeVisible) {
                     // IME not showing, take all touches
                     info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                    return;
                 }
                 if (!mView.isImeRenderingNavButtons()) {
                     // IME showing but not drawing any buttons, take all touches
                     info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                    return;
                 }
             }
 
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 d605c1a..b26b42c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -57,7 +57,6 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
-import com.android.internal.util.LatencyTracker;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -199,7 +198,7 @@
     private final Rect mNavBarOverlayExcludedBounds = new Rect();
     private final Region mExcludeRegion = new Region();
     private final Region mUnrestrictedExcludeRegion = new Region();
-    private final LatencyTracker mLatencyTracker;
+    private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
     private final Provider<BackGestureTfClassifierProvider>
             mBackGestureTfClassifierProviderProvider;
     private final FeatureFlags mFeatureFlags;
@@ -303,6 +302,13 @@
                     mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                 }
+
+                @Override
+                public void setTriggerBack(boolean triggerBack) {
+                    if (mBackAnimation != null) {
+                        mBackAnimation.setTriggerBack(triggerBack);
+                    }
+                }
             };
 
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
@@ -332,7 +338,7 @@
             IWindowManager windowManagerService,
             Optional<Pip> pipOptional,
             FalsingManager falsingManager,
-            LatencyTracker latencyTracker,
+            Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
             FeatureFlags featureFlags) {
         super(broadcastDispatcher);
@@ -351,7 +357,7 @@
         mWindowManagerService = windowManagerService;
         mPipOptional = pipOptional;
         mFalsingManager = falsingManager;
-        mLatencyTracker = latencyTracker;
+        mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
         mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
         mFeatureFlags = featureFlags;
         mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
@@ -576,8 +582,7 @@
             setEdgeBackPlugin(
                     mBackPanelControllerFactory.create(mContext));
         } else {
-            setEdgeBackPlugin(
-                    new NavigationBarEdgePanel(mContext, mLatencyTracker));
+            setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
         }
     }
 
@@ -1084,7 +1089,7 @@
         private final IWindowManager mWindowManagerService;
         private final Optional<Pip> mPipOptional;
         private final FalsingManager mFalsingManager;
-        private final LatencyTracker mLatencyTracker;
+        private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
         private final Provider<BackGestureTfClassifierProvider>
                 mBackGestureTfClassifierProviderProvider;
         private final FeatureFlags mFeatureFlags;
@@ -1104,7 +1109,7 @@
                        IWindowManager windowManagerService,
                        Optional<Pip> pipOptional,
                        FalsingManager falsingManager,
-                       LatencyTracker latencyTracker,
+                       Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
                        Provider<BackGestureTfClassifierProvider>
                                backGestureTfClassifierProviderProvider,
                        FeatureFlags featureFlags) {
@@ -1122,7 +1127,7 @@
             mWindowManagerService = windowManagerService;
             mPipOptional = pipOptional;
             mFalsingManager = falsingManager;
-            mLatencyTracker = latencyTracker;
+            mNavBarEdgePanelProvider = navBarEdgePanelProvider;
             mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
             mFeatureFlags = featureFlags;
         }
@@ -1145,7 +1150,7 @@
                     mWindowManagerService,
                     mPipOptional,
                     mFalsingManager,
-                    mLatencyTracker,
+                    mNavBarEdgePanelProvider,
                     mBackGestureTfClassifierProviderProvider,
                     mFeatureFlags);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 122852f..1230708 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -52,9 +52,9 @@
 
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +62,8 @@
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
 
     private static final String TAG = "NavigationBarEdgePanel";
@@ -282,11 +284,16 @@
             };
     private BackCallback mBackCallback;
 
-    public NavigationBarEdgePanel(Context context, LatencyTracker latencyTracker) {
+    @Inject
+    public NavigationBarEdgePanel(
+            Context context,
+            LatencyTracker latencyTracker,
+            VibratorHelper vibratorHelper,
+            @Background Executor backgroundExecutor) {
         super(context);
 
         mWindowManager = context.getSystemService(WindowManager.class);
-        mVibratorHelper = Dependency.get(VibratorHelper.class);
+        mVibratorHelper = vibratorHelper;
 
         mDensity = context.getResources().getDisplayMetrics().density;
 
@@ -358,7 +365,6 @@
 
         setVisibility(GONE);
 
-        Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -880,6 +886,7 @@
             // Whenever the trigger back state changes the existing translation animation should be
             // cancelled
             mTranslationAnimation.cancel();
+            mBackCallback.setTriggerBack(mTriggerBack);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
index cd36091..1d05874 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
@@ -28,21 +28,29 @@
 public class NonInterceptingScrollView extends ScrollView {
 
     private final int mTouchSlop;
+
     private float mDownY;
     private boolean mScrollEnabled = true;
+    private boolean mPreventingIntercept;
 
     public NonInterceptingScrollView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
+    public boolean isPreventingIntercept() {
+        return mPreventingIntercept;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
+                mPreventingIntercept = false;
                 if (canScrollVertically(1)) {
                     // If we can scroll down, make sure we're not intercepted by the parent
+                    mPreventingIntercept = true;
                     final ViewParent parent = getParent();
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
@@ -62,10 +70,13 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // If there's a touch on this view and we can scroll down, we don't want to be intercepted
         int action = ev.getActionMasked();
+
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                // If we can scroll down, make sure non of our parents intercepts us.
+                mPreventingIntercept = false;
+                // If we can scroll down, make sure none of our parents intercepts us.
                 if (canScrollVertically(1)) {
+                    mPreventingIntercept = true;
                     final ViewParent parent = getParent();
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7b1ddd6..ef87fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -131,6 +131,10 @@
         updateClippingPath();
     }
 
+    public NonInterceptingScrollView getQSPanelContainer() {
+        return mQSPanelContainer;
+    }
+
     public void disable(int state1, int state2, boolean animate) {
         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
         if (disabled == mQsDisabled) return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7d61991..dea7bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.qs;
 
-import android.content.res.Configuration;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
 
+import android.content.res.Configuration;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
@@ -30,6 +35,8 @@
     private final QSPanelController mQsPanelController;
     private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
     private final ConfigurationController mConfigurationController;
+    private final FalsingManager mFalsingManager;
+    private final NonInterceptingScrollView mQSPanelContainer;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -39,14 +46,32 @@
         }
     };
 
+    private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+                if (mQSPanelContainer.isPreventingIntercept()) {
+                    // There's really no action here to take, but we need to tell the FalsingManager
+                    mFalsingManager.isFalseTouch(QS_SWIPE_NESTED);
+                }
+            }
+            return false;
+        }
+    };
+
     @Inject
-    QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
+    QSContainerImplController(
+            QSContainerImpl view,
+            QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
-            ConfigurationController configurationController) {
+            ConfigurationController configurationController,
+            FalsingManager falsingManager) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
+        mFalsingManager = falsingManager;
+        mQSPanelContainer = mView.getQSPanelContainer();
     }
 
     @Override
@@ -62,11 +87,13 @@
     protected void onViewAttached() {
         mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         mConfigurationController.addCallback(mConfigurationListener);
+        mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
     }
 
     @Override
     protected void onViewDetached() {
         mConfigurationController.removeCallback(mConfigurationListener);
+        mQSPanelContainer.setOnTouchListener(null);
     }
 
     public QSContainerImpl getView() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 3820500..7a44058 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -60,6 +60,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -82,7 +83,7 @@
     private static final String EXTRA_VISIBLE = "visible";
 
     private final Rect mQsBounds = new Rect();
-    private final StatusBarStateController mStatusBarStateController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
     private final FalsingManager mFalsingManager;
     private final KeyguardBypassController mBypassController;
     private boolean mQsExpanded;
@@ -159,7 +160,7 @@
      * Progress of pull down from the center of the lock screen.
      * @see com.android.systemui.statusbar.LockscreenShadeTransitionController
      */
-    private float mFullShadeProgress;
+    private float mLockscreenToShadeProgress;
 
     private boolean mOverScrolling;
 
@@ -177,7 +178,7 @@
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             QSTileHost qsTileHost,
-            StatusBarStateController statusBarStateController, CommandQueue commandQueue,
+            SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
             @Named(QS_PANEL) MediaHost qsMediaHost,
             @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
             KeyguardBypassController keyguardBypassController,
@@ -442,20 +443,19 @@
     }
 
     private void updateQsState() {
-        final boolean expanded = mQsExpanded || mInSplitShade;
-        final boolean expandVisually = expanded || mStackScrollerOverscrolling
+        final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
                 || mHeaderAnimating;
-        mQSPanelController.setExpanded(expanded);
+        mQSPanelController.setExpanded(mQsExpanded);
         boolean keyguardShowing = isKeyguardState();
-        mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
+        mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
                 ? View.VISIBLE
                 : View.INVISIBLE);
         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
+                || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
         boolean qsPanelVisible = !mQsDisabled && expandVisually;
-        boolean footerVisible = qsPanelVisible &&  (expanded || !keyguardShowing || mHeaderAnimating
-                || mShowCollapsedOnKeyguard);
+        boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
+                || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
         if (mQSFooterActionController != null) {
             mQSFooterActionController.setVisible(footerVisible);
@@ -463,7 +463,7 @@
             mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
         }
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (expanded && !mStackScrollerOverscrolling));
+                || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
         if (DEBUG) {
             Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible);
@@ -586,7 +586,7 @@
             mTransitioningToFullShade = isTransitioningToFullShade;
             updateShowCollapsedOnKeyguard();
         }
-        mFullShadeProgress = qsTransitionFraction;
+        mLockscreenToShadeProgress = qsTransitionFraction;
         setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation,
                 isTransitioningToFullShade ? qsSquishinessFraction : mSquishinessFraction);
     }
@@ -691,7 +691,6 @@
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
         }
-        mQqsMediaHost.setSquishFraction(mSquishinessFraction);
     }
 
     private void setAlphaAnimationProgress(float progress) {
@@ -711,10 +710,13 @@
         }
         if (mInSplitShade) {
             // Large screens in landscape.
-            if (mTransitioningToFullShade || isKeyguardState()) {
+            // Need to check upcoming state as for unlocked -> AOD transition current state is
+            // not updated yet, but we're transitioning and UI should already follow KEYGUARD state
+            if (mTransitioningToFullShade || mStatusBarStateController.getCurrentOrUpcomingState()
+                    == StatusBarState.KEYGUARD) {
                 // Always use "mFullShadeProgress" on keyguard, because
                 // "panelExpansionFractions" is always 1 on keyguard split shade.
-                return mFullShadeProgress;
+                return mLockscreenToShadeProgress;
             } else {
                 return panelExpansionFraction;
             }
@@ -723,7 +725,7 @@
         if (mTransitioningToFullShade) {
             // Only use this value during the standard lock screen shade expansion. During the
             // "quick" expansion from top, this value is 0.
-            return mFullShadeProgress;
+            return mLockscreenToShadeProgress;
         } else {
             return panelExpansionFraction;
         }
@@ -931,7 +933,7 @@
         indentingPw.println("mLastHeaderTranslation: " + mLastHeaderTranslation);
         indentingPw.println("mInSplitShade: " + mInSplitShade);
         indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade);
-        indentingPw.println("mFullShadeProgress: " + mFullShadeProgress);
+        indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress);
         indentingPw.println("mOverScrolling: " + mOverScrolling);
         indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing());
         View view = getView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 448e180..184089f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -473,6 +473,8 @@
                     ? Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0) : 0;
             layoutParams.topMargin = mediaNeedsTopMargin() && !horizontal
                     ? mMediaTopMargin : 0;
+            // Call setLayoutParams explicitly to ensure that requestLayout happens
+            hostView.setLayoutParams(layoutParams);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 59b871c..18bd6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -16,19 +16,17 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.classifier.Classifier.QS_SWIPE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
-import android.content.res.Configuration;
 import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostState;
@@ -61,22 +59,11 @@
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
-    private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
-            new QSPanel.OnConfigurationChangedListener() {
-        @Override
-        public void onConfigurationChange(Configuration newConfig) {
-            mView.updateResources();
-            if (mView.isListening()) {
-                refreshAllTiles();
-            }
-        }
-    };
-
     private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                mFalsingManager.isFalseTouch(QS_SWIPE);
+                mFalsingManager.isFalseTouch(QS_SWIPE_SIDE);
             }
             return false;
         }
@@ -88,14 +75,13 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
             QSTileRevealController.Factory qsTileRevealControllerFactory,
-            DumpManager dumpManager, MediaCarouselController mediaCarouselController,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+            DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
@@ -130,7 +116,6 @@
         if (mView.isListening()) {
             refreshAllTiles();
         }
-        mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
 
@@ -147,11 +132,18 @@
     @Override
     protected void onViewDetached() {
         mTunerService.removeTunable(mView);
-        mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
         mBrightnessMirrorHandler.onQsPanelDettached();
         super.onViewDetached();
     }
 
+    @Override
+    protected void onConfigurationChanged() {
+        mView.updateResources();
+        if (mView.isListening()) {
+            refreshAllTiles();
+        }
+    }
+
     /** */
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 6e4c858..ded466a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -71,7 +70,6 @@
     private final UiEventLogger mUiEventLogger;
     private final QSLogger mQSLogger;
     private final DumpManager mDumpManager;
-    private final MediaCarouselController mMediaCarouselController;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
     protected boolean mShouldUseSplitNotificationShade;
 
@@ -101,11 +99,11 @@
                                     + newConfig.windowConfiguration);
                     mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
                             mView.getDumpableTag());
-                    onConfigurationChanged();
                     if (newConfig.orientation != mLastOrientation) {
                         mLastOrientation = newConfig.orientation;
                         switchTileLayout(false);
                     }
+                    onConfigurationChanged();
                 }
             };
 
@@ -133,8 +131,7 @@
             MetricsLogger metricsLogger,
             UiEventLogger uiEventLogger,
             QSLogger qsLogger,
-            DumpManager dumpManager,
-            MediaCarouselController mediaCarouselController
+            DumpManager dumpManager
     ) {
         super(view);
         mHost = host;
@@ -147,7 +144,6 @@
         mDumpManager = dumpManager;
         mShouldUseSplitNotificationShade =
                 LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
-        mMediaCarouselController = mediaCarouselController;
     }
 
     @Override
@@ -165,7 +161,6 @@
 
     public void setSquishinessFraction(float squishinessFraction) {
         mView.setSquishinessFraction(squishinessFraction);
-        mMediaCarouselController.setSquishinessFraction(squishinessFraction);
     }
 
     @Override
@@ -422,6 +417,8 @@
         }
         if (mMediaHost != null) {
             pw.println("  media bounds: " + mMediaHost.getCurrentBounds());
+            pw.println("  horizontal layout: " + mUsingHorizontalLayout);
+            pw.println("  last orientation: " + mLastOrientation);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index bd75c75..ae6ed20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -444,7 +444,7 @@
         mShouldUseSettingsButton.set(false);
         mBgHandler.post(() -> {
             String settingsButtonText = getSettingsButton();
-            final View dialogView = createDialogView();
+            final View dialogView = createDialogView(quickSettingsContext);
             mMainHandler.post(() -> {
                 mDialog = new SystemUIDialog(quickSettingsContext, 0);
                 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -469,14 +469,14 @@
     }
 
     @VisibleForTesting
-    View createDialogView() {
+    View createDialogView(Context quickSettingsContext) {
         if (mSecurityController.isParentalControlsEnabled()) {
             return createParentalControlsDialogView();
         }
-        return createOrganizationDialogView();
+        return createOrganizationDialogView(quickSettingsContext);
     }
 
-    private View createOrganizationDialogView() {
+    private View createOrganizationDialogView(Context quickSettingsContext) {
         final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
         final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
         final CharSequence deviceOwnerOrganization =
@@ -487,7 +487,7 @@
         final String vpnName = mSecurityController.getPrimaryVpnName();
         final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
 
-        View dialogView = LayoutInflater.from(mContext)
+        View dialogView = LayoutInflater.from(quickSettingsContext)
                 .inflate(R.layout.quick_settings_footer_dialog, null, false);
 
         // device management section
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index c86e6e8..9739974 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,7 +26,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
@@ -46,14 +45,6 @@
 @QSScope
 public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
 
-    private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
-            newConfig -> {
-                int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
-                if (newMaxTiles != mView.getNumQuickTiles()) {
-                    setMaxTiles(newMaxTiles);
-                }
-            };
-
     private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
 
     @Inject
@@ -64,10 +55,10 @@
             @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
                     Provider<Boolean> usingCollapsedLandscapeMediaProvider,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, MediaCarouselController mediaCarouselController
+            DumpManager dumpManager
     ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+                uiEventLogger, qsLogger, dumpManager);
         mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
     }
 
@@ -99,13 +90,11 @@
     @Override
     protected void onViewAttached() {
         super.onViewAttached();
-        mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
     }
 
     @Override
     protected void onViewDetached() {
         super.onViewDetached();
-        mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
     }
 
     private void setMaxTiles(int parseNumTiles) {
@@ -115,6 +104,10 @@
 
     @Override
     protected void onConfigurationChanged() {
+        int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
+        if (newMaxTiles != mView.getNumQuickTiles()) {
+            setMaxTiles(newMaxTiles);
+        }
         updateMediaExpansion();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 264edb1..84d7e65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -410,9 +410,9 @@
         // If forceExpanded (we are opening QS from lockscreen), the animators have been set to
         // position = 1f.
         if (forceExpanded) {
-            setTranslationY(panelTranslationY);
+            setAlpha(expansionFraction);
         } else {
-            setTranslationY(0);
+            setAlpha(1);
         }
 
         mKeyguardExpansionFraction = keyguardExpansionFraction;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index b585961..ccaab1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.VariableDateViewController;
@@ -104,7 +105,7 @@
                 mView.requireViewById(R.id.date_clock)
         );
 
-        mIconManager = tintedIconManagerFactory.create(mIconContainer);
+        mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
         mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
         mColorExtractor = colorExtractor;
         mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index b6f6e93..624def6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -51,8 +51,6 @@
 /** Quick settings tile: Hotspot **/
 public class HotspotTile extends QSTileImpl<BooleanState> {
 
-    private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
-
     private final HotspotController mHotspotController;
     private final DataSaverController mDataSaverController;
 
@@ -129,9 +127,6 @@
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
-        if (state.slash == null) {
-            state.slash = new SlashState();
-        }
 
         final int numConnectedDevices;
         final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient();
@@ -150,13 +145,14 @@
             isDataSaverEnabled = mDataSaverController.isDataSaverEnabled();
         }
 
-        state.icon = mEnabledStatic;
         state.label = mContext.getString(R.string.quick_settings_hotspot_label);
         state.isTransient = isTransient;
-        state.slash.isSlashed = !state.value && !state.isTransient;
         if (state.isTransient) {
             state.icon = ResourceIcon.get(
-                    com.android.internal.R.drawable.ic_hotspot_transient_animation);
+                    R.drawable.qs_hotspot_icon_search);
+        } else {
+            state.icon = ResourceIcon.get(state.value
+                    ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
         }
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 0ec4eef..d2d5063 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.qs.tiles;
 
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -42,6 +39,7 @@
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.user.UserSwitchDialogController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.user.data.source.UserRecord;
 
@@ -73,7 +71,8 @@
         mAdapter.refresh();
     }
 
-    public static class Adapter extends UserSwitcherController.BaseUserAdapter
+    /** Provides views for user detail items. */
+    public static class Adapter extends BaseUserSwitcherAdapter
             implements OnClickListener {
 
         private final Context mContext;
@@ -135,9 +134,9 @@
                 v.bind(name, drawable, item.info.id);
             }
             v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
+            v.setDisabledByAdmin(item.isDisabledByAdmin());
             v.setEnabled(item.isSwitchToEnabled);
-            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+            UserSwitcherController.setSelectableAlpha(v);
 
             if (item.isCurrent) {
                 mCurrentUserView = v;
@@ -174,16 +173,16 @@
             Trace.beginSection("UserDetailView.Adapter#onClick");
             UserRecord userRecord =
                     (UserRecord) view.getTag();
-            if (mController.isDisabledByAdmin(userRecord)) {
+            if (userRecord.isDisabledByAdmin()) {
                 final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                        mContext, mController.getEnforcedAdmin(userRecord));
+                        mContext, userRecord.enforcedAdmin);
                 mController.startActivity(intent);
             } else if (userRecord.isSwitchToEnabled) {
                 MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
                 if (!userRecord.isAddUser
                         && !userRecord.isRestricted
-                        && !mController.isDisabledByAdmin(userRecord)) {
+                        && !userRecord.isDisabledByAdmin()) {
                     if (mCurrentUserView != null) {
                         mCurrentUserView.setActivated(false);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 3788ad9..66be00d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,13 +25,6 @@
 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_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
 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_UNLOCK_ANIMATION_CONTROLLER;
@@ -108,14 +101,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -147,9 +133,8 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
-    private final Optional<Pip> mPipOptional;
+    private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    private final Optional<SplitScreen> mSplitScreenOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -159,13 +144,8 @@
     private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
     private final Intent mQuickStepIntent;
     private final ScreenshotHelper mScreenshotHelper;
-    private final Optional<OneHanded> mOneHandedOptional;
     private final CommandQueue mCommandQueue;
-    private final ShellTransitions mShellTransitions;
-    private final Optional<StartingSurface> mStartingSurface;
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
-    private final Optional<RecentTasks> mRecentTasks;
-    private final Optional<BackAnimation> mBackAnimation;
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
@@ -336,14 +316,6 @@
         }
 
         @Override
-        public void notifySwipeToHomeFinished() {
-            verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
-                    mPipOptional.ifPresent(
-                            pip -> pip.setPinnedStackAnimationType(
-                                    PipAnimationController.ANIM_TYPE_ALPHA)));
-        }
-
-        @Override
         public void notifySwipeUpGestureStarted() {
             verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
                     notifySwipeUpGestureStartedInternal());
@@ -382,7 +354,6 @@
                     mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::togglePanel));
         }
 
-
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -459,30 +430,10 @@
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
             params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
             params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
-            mPipOptional.ifPresent((pip) -> params.putBinder(
-                    KEY_EXTRA_SHELL_PIP,
-                    pip.createExternalInterface().asBinder()));
-            mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
-                    KEY_EXTRA_SHELL_SPLIT_SCREEN,
-                    splitscreen.createExternalInterface().asBinder()));
-            mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
-                    KEY_EXTRA_SHELL_ONE_HANDED,
-                    onehanded.createExternalInterface().asBinder()));
-            params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
-                    mShellTransitions.createExternalInterface().asBinder());
-            mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
-                    KEY_EXTRA_SHELL_STARTING_WINDOW,
-                    startingwindow.createExternalInterface().asBinder()));
-            params.putBinder(
-                    KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+            params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
                     mSysuiUnlockAnimationController.asBinder());
-            mRecentTasks.ifPresent(recentTasks -> params.putBinder(
-                    KEY_EXTRA_RECENT_TASKS,
-                    recentTasks.createExternalInterface().asBinder()));
-            mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
-                    KEY_EXTRA_SHELL_BACK_ANIMATION,
-                    backAnimation.createExternalInterface().asBinder()));
+            // Add all the interfaces exposed by the shell
+            mShellInterface.createExternalInterfaces(params);
 
             try {
                 Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -556,19 +507,14 @@
 
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
-    public OverviewProxyService(Context context, CommandQueue commandQueue,
+    public OverviewProxyService(Context context,
+            CommandQueue commandQueue,
+            ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            Optional<Pip> pipOptional,
-            Optional<SplitScreen> splitScreenOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<RecentTasks> recentTasks,
-            Optional<BackAnimation> backAnimation,
-            Optional<StartingSurface> startingSurface,
             BroadcastDispatcher broadcastDispatcher,
-            ShellTransitions shellTransitions,
             ScreenLifecycle screenLifecycle,
             UiEventLogger uiEventLogger,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -582,7 +528,7 @@
         }
 
         mContext = context;
-        mPipOptional = pipOptional;
+        mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
@@ -597,10 +543,6 @@
                 .supportsRoundedCornersOnWindows(mContext.getResources());
         mSysUiState = sysUiState;
         mSysUiState.addCallback(this::notifySystemUiStateFlags);
-        mOneHandedOptional = oneHandedOptional;
-        mShellTransitions = shellTransitions;
-        mRecentTasks = recentTasks;
-        mBackAnimation = backAnimation;
         mUiEventLogger = uiEventLogger;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -630,8 +572,6 @@
         });
         mCommandQueue = commandQueue;
 
-        mSplitScreenOptional = splitScreenOptional;
-
         // Listen for user setup
         startTracking();
 
@@ -640,7 +580,6 @@
         // Connect to the service
         updateEnabledState();
         startConnectionToCurrentUser();
-        mStartingSurface = startingSurface;
         mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
 
         // Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
index ac5640b..67e9a87 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -19,15 +19,9 @@
 import android.app.IActivityTaskManager
 import android.graphics.Bitmap
 import android.graphics.Rect
-import android.hardware.display.DisplayManager
-import android.os.IBinder
-import android.util.Log
-import android.view.DisplayAddress
-import android.view.SurfaceControl
+import android.view.IWindowManager
 import android.window.ScreenCapture
-import android.window.ScreenCapture.DisplayCaptureArgs
-import android.window.ScreenCapture.ScreenshotHardwareBuffer
-import androidx.annotation.VisibleForTesting
+import android.window.ScreenCapture.CaptureArgs
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
@@ -38,18 +32,18 @@
 
 @SysUISingleton
 open class ImageCaptureImpl @Inject constructor(
-    private val displayManager: DisplayManager,
+    private val windowManager: IWindowManager,
     private val atmService: IActivityTaskManager,
     @Background private val bgContext: CoroutineDispatcher
 ) : ImageCapture {
 
     override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
-        val width = crop?.width() ?: 0
-        val height = crop?.height() ?: 0
-        val sourceCrop = crop ?: Rect()
-        val displayToken = physicalDisplayToken(displayId) ?: return null
-        val buffer = captureDisplay(displayToken, width, height, sourceCrop)
-
+        val captureArgs = CaptureArgs.Builder()
+            .setSourceCrop(crop)
+            .build()
+        val syncScreenCapture = ScreenCapture.createSyncCaptureListener()
+        windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture.first)
+        val buffer = syncScreenCapture.second.get()
         return buffer?.asBitmap()
     }
 
@@ -57,34 +51,4 @@
         val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
         return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
     }
-
-    @VisibleForTesting
-    open fun physicalDisplayToken(displayId: Int): IBinder? {
-        val display = displayManager.getDisplay(displayId)
-        if (display == null) {
-            Log.e(TAG, "No display with id: $displayId")
-            return null
-        }
-        val address = display.address
-        if (address !is DisplayAddress.Physical) {
-            Log.e(TAG, "Display does not have a physical address: $display")
-            return null
-        }
-        return SurfaceControl.getPhysicalDisplayToken(address.physicalDisplayId)
-    }
-
-    @VisibleForTesting
-    open fun captureDisplay(
-        displayToken: IBinder,
-        width: Int,
-        height: Int,
-        crop: Rect
-    ): ScreenshotHardwareBuffer? {
-        val captureArgs =
-            DisplayCaptureArgs.Builder(displayToken)
-                .setSize(width, height)
-                .setSourceCrop(crop)
-                .build()
-        return ScreenCapture.captureDisplay(captureArgs)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 55602a9..e3658de 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -19,6 +19,7 @@
 import static android.os.FileUtils.closeQuietly;
 
 import android.annotation.IntRange;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.graphics.Bitmap;
@@ -29,6 +30,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.util.Log;
 
@@ -142,8 +144,9 @@
      *
      * @return a listenable future result
      */
-    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) {
-        return export(executor, requestId, bitmap, ZonedDateTime.now());
+    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+            UserHandle owner) {
+        return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
     }
 
     /**
@@ -155,10 +158,10 @@
      * @return a listenable future result
      */
     ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
-            ZonedDateTime captureTime) {
+            ZonedDateTime captureTime, UserHandle owner) {
 
         final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true);
+                mQuality, /* publish */ true, owner);
 
         return CallbackToFutureAdapter.getFuture(
                 (completer) -> {
@@ -174,28 +177,6 @@
         );
     }
 
-    /**
-     * Delete the entry.
-     *
-     * @param executor the thread for execution
-     * @param uri the uri of the image to publish
-     *
-     * @return a listenable future result
-     */
-    ListenableFuture<Result> delete(Executor executor, Uri uri) {
-        return CallbackToFutureAdapter.getFuture((completer) -> {
-            executor.execute(() -> {
-                mResolver.delete(uri, null);
-
-                Result result = new Result();
-                result.uri = uri;
-                result.deleted = true;
-                completer.set(result);
-            });
-            return "ContentResolver#delete";
-        });
-    }
-
     static class Result {
         Uri uri;
         UUID requestId;
@@ -203,7 +184,6 @@
         long timestamp;
         CompressFormat format;
         boolean published;
-        boolean deleted;
 
         @Override
         public String toString() {
@@ -214,7 +194,6 @@
             sb.append(", timestamp=").append(timestamp);
             sb.append(", format=").append(format);
             sb.append(", published=").append(published);
-            sb.append(", deleted=").append(deleted);
             sb.append('}');
             return sb.toString();
         }
@@ -227,17 +206,19 @@
         private final ZonedDateTime mCaptureTime;
         private final CompressFormat mFormat;
         private final int mQuality;
+        private final UserHandle mOwner;
         private final String mFileName;
         private final boolean mPublish;
 
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
-                CompressFormat format, int quality, boolean publish) {
+                CompressFormat format, int quality, boolean publish, UserHandle owner) {
             mResolver = resolver;
             mRequestId = requestId;
             mBitmap = bitmap;
             mCaptureTime = captureTime;
             mFormat = format;
             mQuality = quality;
+            mOwner = owner;
             mFileName = createFilename(mCaptureTime, mFormat);
             mPublish = publish;
         }
@@ -253,7 +234,7 @@
                     start = Instant.now();
                 }
 
-                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName);
+                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner);
                 throwIfInterrupted();
 
                 writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -297,15 +278,20 @@
     }
 
     private static Uri createEntry(ContentResolver resolver, CompressFormat format,
-            ZonedDateTime time, String fileName) throws ImageExportException {
+            ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException {
         Trace.beginSection("ImageExporter_createEntry");
         try {
             final ContentValues values = createMetadata(time, format, fileName);
 
-            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+            if (UserHandle.myUserId() != owner.getIdentifier()) {
+                baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+            }
+            Uri uri = resolver.insert(baseUri, values);
             if (uri == null) {
                 throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
             }
+            Log.d(TAG, "Inserted new URI: " + uri);
             return uri;
         } finally {
             Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ba6e98e..8bf956b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -387,7 +388,9 @@
 
         mOutputBitmap = renderBitmap(drawable, bounds);
         ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
-                mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now());
+                mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
+                // TODO: Owner must match the owner of the captured window.
+                Process.myUserHandle());
         exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index f248d69..077ad35 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -48,6 +48,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -71,6 +73,7 @@
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
     private final Context mContext;
+    private FeatureFlags mFlags;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final ScreenshotController.SaveImageInBackgroundData mParams;
     private final ScreenshotController.SavedImageData mImageData;
@@ -84,7 +87,10 @@
     private final ImageExporter mImageExporter;
     private long mImageTime;
 
-    SaveImageInBackgroundTask(Context context, ImageExporter exporter,
+    SaveImageInBackgroundTask(
+            Context context,
+            FeatureFlags flags,
+            ImageExporter exporter,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotController.SaveImageInBackgroundData data,
             Supplier<ActionTransition> sharedElementTransition,
@@ -92,6 +98,7 @@
                     screenshotNotificationSmartActionsProvider
     ) {
         mContext = context;
+        mFlags = flags;
         mScreenshotSmartActions = screenshotSmartActions;
         mImageData = new ScreenshotController.SavedImageData();
         mQuickShareData = new ScreenshotController.QuickShareData();
@@ -117,7 +124,8 @@
         }
         // TODO: move to constructor / from ScreenshotRequest
         final UUID requestId = UUID.randomUUID();
-        final UserHandle user = getUserHandleOfForegroundApplication(mContext);
+        final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+                ? mParams.owner : getUserHandleOfForegroundApplication(mContext);
 
         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
 
@@ -133,8 +141,9 @@
 
             // Call synchronously here since already on a background thread.
             ListenableFuture<ImageExporter.Result> future =
-                    mImageExporter.export(Runnable::run, requestId, image);
+                    mImageExporter.export(Runnable::run, requestId, image, mParams.owner);
             ImageExporter.Result result = future.get();
+            Log.d(TAG, "Saved screenshot: " + result);
             final Uri uri = result.uri;
             mImageTime = result.timestamp;
 
@@ -157,6 +166,7 @@
             }
 
             mImageData.uri = uri;
+            mImageData.owner = user;
             mImageData.smartActions = smartActions;
             mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
             mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 69ee8e8..df32d20 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,6 +34,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -57,7 +58,9 @@
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -90,6 +93,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.util.Assert;
@@ -151,6 +155,7 @@
         public Consumer<Uri> finisher;
         public ScreenshotController.ActionsReadyListener mActionsReadyListener;
         public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
+        public UserHandle owner;
 
         void clearImage() {
             image = null;
@@ -167,6 +172,8 @@
         public Notification.Action deleteAction;
         public List<Notification.Action> smartActions;
         public Notification.Action quickShareAction;
+        public UserHandle owner;
+
 
         /**
          * POD for shared element transition.
@@ -242,6 +249,7 @@
     private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
 
     private final WindowContext mContext;
+    private final FeatureFlags mFlags;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final UiEventLogger mUiEventLogger;
@@ -288,6 +296,7 @@
     @Inject
     ScreenshotController(
             Context context,
+            FeatureFlags flags,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController screenshotNotificationsController,
             ScrollCaptureClient scrollCaptureClient,
@@ -331,6 +340,7 @@
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mFlags = flags;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -377,7 +387,6 @@
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
             Consumer<Uri> finisher, RequestCallback requestCallback) {
-        // TODO: use task Id, userId, topComponent for smart handler
         Assert.isMainThread();
         if (screenshot == null) {
             Log.e(TAG, "Got null bitmap from screenshot message");
@@ -395,7 +404,7 @@
         }
         mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
-                showFlash);
+                showFlash, UserHandle.of(userId));
     }
 
     /**
@@ -543,18 +552,21 @@
             return;
         }
 
-        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
+        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
+                Process.myUserHandle());
 
         mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
                 ClipboardOverlayController.SELF_PERMISSION);
     }
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
-            Insets screenInsets, ComponentName topComponent, boolean showFlash) {
+            Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
         withWindowAttached(() ->
                 mScreenshotView.announceForAccessibility(
                         mContext.getResources().getString(R.string.screenshot_saving_title)));
 
+        mScreenshotView.reset();
+
         if (mScreenshotView.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
             if (!mScreenshotView.isDismissing()) {
@@ -564,7 +576,6 @@
                 Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
                         + "(dismissing=" + mScreenshotView.isDismissing() + ")");
             }
-            mScreenshotView.reset();
         }
         mPackageName = topComponent == null ? "" : topComponent.getPackageName();
         mScreenshotView.setPackageName(mPackageName);
@@ -574,11 +585,11 @@
 
         mScreenBitmap = screenshot;
 
-        if (!isUserSetupComplete()) {
+        if (!isUserSetupComplete(owner)) {
             Log.w(TAG, "User setup not complete, displaying toast only");
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
             // and sharing shouldn't be exposed to the user.
-            saveScreenshotAndToast(finisher);
+            saveScreenshotAndToast(owner, finisher);
             return;
         }
 
@@ -586,7 +597,7 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
-        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
+        saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
                 this::showUiOnQuickShareActionReady);
 
         // The window is focusable by default
@@ -852,11 +863,12 @@
      * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
      * failure).
      */
-    private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+    private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSound();
 
         saveScreenshotInWorkerThread(
+                owner,
                 /* onComplete */ finisher,
                 /* actionsReadyListener */ imageData -> {
                     if (DEBUG_CALLBACK) {
@@ -924,9 +936,11 @@
     /**
      * Creates a new worker thread and saves the screenshot to the media store.
      */
-    private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
-            @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
-            @Nullable ScreenshotController.QuickShareActionReadyListener
+    private void saveScreenshotInWorkerThread(
+            UserHandle owner,
+            @NonNull Consumer<Uri> finisher,
+            @Nullable ActionsReadyListener actionsReadyListener,
+            @Nullable QuickShareActionReadyListener
                     quickShareActionsReadyListener) {
         ScreenshotController.SaveImageInBackgroundData
                 data = new ScreenshotController.SaveImageInBackgroundData();
@@ -934,13 +948,14 @@
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
         data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
+        data.owner = owner;
 
         if (mSaveInBgTask != null) {
             // just log success/failure for the pre-existing screenshot
             mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
         }
 
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
                 mScreenshotSmartActions, data, getActionTransitionSupplier(),
                 mScreenshotNotificationSmartActionsProvider);
         mSaveInBgTask.execute();
@@ -959,6 +974,15 @@
         mScreenshotHandler.resetTimeout();
 
         if (imageData.uri != null) {
+            if (!imageData.owner.equals(Process.myUserHandle())) {
+                // TODO: Handle non-primary user ownership (e.g. Work Profile)
+                // This image is owned by another user. Special treatment will be
+                // required in the UI (badging) as well as sending intents which can
+                // correctly forward those URIs on to be read (actions).
+
+                Log.d(TAG, "*** Screenshot saved to a non-primary user ("
+                        + imageData.owner + ") as " + imageData.uri);
+            }
             mScreenshotHandler.post(() -> {
                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                     mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@@ -1032,9 +1056,9 @@
         }
     }
 
-    private boolean isUserSetupComplete() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    private boolean isUserSetupComplete(UserHandle owner) {
+        return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+                        .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index c2a5060..3a35286 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -68,7 +68,9 @@
     }
 
     override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
-        return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+        val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+        Log.d(TAG, "isManagedProfile: $managed")
+        return managed
     }
 
     private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 9654e03..793085a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -19,14 +19,14 @@
 import android.content.Intent
 import android.os.IBinder
 import android.util.Log
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import com.android.systemui.shade.ShadeExpansionStateManager
 import javax.inject.Inject
 
 /**
  * Provides state from the main SystemUI process on behalf of the Screenshot process.
  */
 internal class ScreenshotProxyService @Inject constructor(
-    private val mExpansionMgr: PanelExpansionStateManager
+    private val mExpansionMgr: ShadeExpansionStateManager
 ) : Service() {
 
     private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 5e7fc6f..360fc87 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -1006,6 +1006,7 @@
         // Clear any references to the bitmap
         mScreenshotPreview.setImageDrawable(null);
         mScreenshotPreview.setVisibility(View.INVISIBLE);
+        mScreenshotPreview.setAlpha(1f);
         mScreenshotPreviewBorder.setAlpha(0);
         mPendingSharedTransition = false;
         mActionsContainerBackground.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 83b60fb..30a0b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -78,6 +78,7 @@
     static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
         private final Session mSession;
+        // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
 
         LongScreenshot(Session session, ImageTileSet imageTileSet) {
             mSession = session;
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 a22fda7..6e9f859 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
 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.widget.FrameLayout;
@@ -76,6 +77,12 @@
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
         // The brightness mirror container is INVISIBLE by default.
         frame.setVisibility(View.VISIBLE);
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) frame.getLayoutParams();
+        int horizontalMargin =
+                getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+        lp.leftMargin = horizontalMargin;
+        lp.rightMargin = horizontalMargin;
+        frame.setLayoutParams(lp);
 
         BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
         controller.init();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index fe40d4c..d3ed474 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
@@ -261,7 +262,7 @@
         batteryMeterViewController.ignoreTunerUpdates()
         batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
 
-        iconManager = tintedIconManagerFactory.create(iconContainer)
+        iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
         iconManager.setTint(
             Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
         )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1011a6d..c337dea 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -28,6 +28,9 @@
 import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+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;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -36,9 +39,6 @@
 import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
 import android.animation.Animator;
@@ -202,8 +202,6 @@
 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.phone.panelstate.PanelExpansionStateManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -425,11 +423,10 @@
             new KeyguardClockPositionAlgorithm.Result();
     private boolean mIsExpanding;
 
-    private boolean mBlockTouches;
-
     /**
      * Determines if QS should be already expanded when expanding shade.
      * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
+     * It needs to be set when movement starts as it resets at the end of expansion/collapse.
      */
     @VisibleForTesting
     boolean mQsExpandImmediate;
@@ -761,7 +758,7 @@
             LargeScreenShadeHeaderController largeScreenShadeHeaderController,
             ScreenOffAnimationController screenOffAnimationController,
             LockscreenGestureLogger lockscreenGestureLogger,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             NotificationRemoteInputManager remoteInputManager,
             Optional<SysUIUnfoldComponent> unfoldComponent,
             InteractionJankMonitor interactionJankMonitor,
@@ -790,7 +787,7 @@
                 flingAnimationUtilsBuilder.get(),
                 statusBarTouchableRegionManager,
                 lockscreenGestureLogger,
-                panelExpansionStateManager,
+                shadeExpansionStateManager,
                 ambientState,
                 interactionJankMonitor,
                 shadeLogger,
@@ -861,7 +858,7 @@
                 new DynamicPrivacyControlListener();
         dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
 
-        panelExpansionStateManager.addStateListener(this::onPanelStateChanged);
+        shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
 
         mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
         mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
@@ -1692,7 +1689,6 @@
 
     public void resetViews(boolean animate) {
         mIsLaunchTransitionFinished = false;
-        mBlockTouches = false;
         mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         if (animate && !isFullyCollapsed()) {
@@ -1737,8 +1733,10 @@
     }
 
     private void setQsExpandImmediate(boolean expandImmediate) {
-        mQsExpandImmediate = expandImmediate;
-        mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+        if (expandImmediate != mQsExpandImmediate) {
+            mQsExpandImmediate = expandImmediate;
+            mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+        }
     }
 
     private void setShowShelfOnly(boolean shelfOnly) {
@@ -2230,7 +2228,8 @@
             if (cancel) {
                 collapse(false /* delayed */, 1.0f /* speedUpFactor */);
             } else {
-                maybeVibrateOnOpening();
+                // Window never will receive touch events that typically trigger haptic on open.
+                maybeVibrateOnOpening(false /* openingWithTouch */);
                 fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
             }
             onTrackingStopped(false);
@@ -2478,17 +2477,23 @@
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
         mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
 
-        // updateQsExpansion will get called whenever mTransitionToFullShadeProgress or
-        // mLockscreenShadeTransitionController.getDragProgress change.
-        // When in lockscreen, getDragProgress indicates the true expanded fraction of QS
-        float shadeExpandedFraction = mTransitioningToFullShadeProgress > 0
-                ? mLockscreenShadeTransitionController.getQSDragProgress()
+        float shadeExpandedFraction = isOnKeyguard()
+                ? getLockscreenShadeDragProgress()
                 : getExpandedFraction();
         mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
         mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
     }
 
+    private float getLockscreenShadeDragProgress() {
+        // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
+        // transition. If that's not the case we should follow QS expansion fraction for when
+        // user is pulling from the same top to go directly to expanded QS
+        return mTransitioningToFullShadeProgress > 0
+                ? mLockscreenShadeTransitionController.getQSDragProgress()
+                : computeQsExpansionFraction();
+    }
+
     private void onStackYChanged(boolean shouldAnimate) {
         if (mQs != null) {
             if (shouldAnimate) {
@@ -3123,26 +3128,24 @@
         }
         if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
                 && !mQsExpansionFromOverscroll)) {
-            float t;
-            if (mKeyguardShowing) {
-
+            float qsExpansionFraction;
+            if (mSplitShadeEnabled) {
+                qsExpansionFraction = 1;
+            } else if (mKeyguardShowing) {
                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / (getMaxPanelHeight());
+                qsExpansionFraction = expandedHeight / (getMaxPanelHeight());
             } else {
                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
                 // minimum QS expansion + minStackHeight
-                float
-                        panelHeightQsCollapsed =
+                float panelHeightQsCollapsed =
                         mNotificationStackScrollLayoutController.getIntrinsicPadding()
                                 + mNotificationStackScrollLayoutController.getLayoutMinHeight();
                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
-                t =
-                        (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
-                                - panelHeightQsCollapsed);
+                qsExpansionFraction = (expandedHeight - panelHeightQsCollapsed)
+                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
             }
-            float
-                    targetHeight =
-                    mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+            float targetHeight = mQsMinExpansionHeight
+                    + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
             setQsExpansion(targetHeight);
         }
         updateExpandedHeight(expandedHeight);
@@ -3328,7 +3331,11 @@
         } else {
             setListening(true);
         }
-        setQsExpandImmediate(false);
+        if (mBarState != SHADE) {
+            // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
+            // on keyguard panel state is always OPEN so we need to have that extra update
+            setQsExpandImmediate(false);
+        }
         setShowShelfOnly(false);
         mTwoFingerQsExpandPossible = false;
         updateTrackingHeadsUp(null);
@@ -4186,7 +4193,7 @@
                             "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
                                     + "," + event.getY() + ")");
                 }
-                if (mBlockTouches || mQs.disallowPanelTouches()) {
+                if (mQs.disallowPanelTouches()) {
                     return false;
                 }
                 initDownStates(event);
@@ -4229,8 +4236,7 @@
                 }
 
 
-                if (mBlockTouches || (mQsFullyExpanded && mQs != null
-                        && mQs.disallowPanelTouches())) {
+                if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
                     return false;
                 }
 
@@ -4677,12 +4683,19 @@
                     }
                 }
             } else {
+                // this else branch means we are doing one of:
+                //  - from KEYGUARD and SHADE (but not expanded shade)
+                //  - from SHADE to KEYGUARD
+                //  - from SHADE_LOCKED to SHADE
+                //  - getting notified again about the current SHADE or KEYGUARD state
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
                 if (!animatingUnlockedShadeToKeyguard) {
                     // Only make the status bar visible if we're not animating the screen off, since
                     // we only want to be showing the clock/notifications during the animation.
+                    mShadeLog.v("Updating keyguard status bar state to "
+                            + (keyguardShowing ? "visible" : "invisible"));
                     mKeyguardStatusBarViewController.updateViewState(
                             /* alpha= */ 1f,
                             keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -4748,9 +4761,7 @@
 
                 @Override
                 public float getLockscreenShadeDragProgress() {
-                    return mTransitioningToFullShadeProgress > 0
-                            ? mLockscreenShadeTransitionController.getQSDragProgress()
-                            : computeQsExpansionFraction();
+                    return NotificationPanelViewController.this.getLockscreenShadeDragProgress();
                 }
             };
 
@@ -4987,6 +4998,7 @@
         updateQSExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
+            setQsExpandImmediate(false);
             mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         }
         if (state == STATE_OPENING) {
@@ -4999,6 +5011,7 @@
             mCentralSurfaces.makeExpandedVisible(false);
         }
         if (state == STATE_CLOSED) {
+            setQsExpandImmediate(false);
             // Close the status bar in the next frame so we can show the end of the
             // animation.
             mView.post(mMaybeHideExpandedRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 8d74a09..65bd58d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,10 +31,15 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 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.ui.binder.KeyguardBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -47,7 +52,6 @@
 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.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 
 import java.io.PrintWriter;
@@ -86,7 +90,7 @@
     private boolean mExpandingBelowNotch;
     private final DockManager mDockManager;
     private final NotificationPanelViewController mNotificationPanelViewController;
-    private final PanelExpansionStateManager mPanelExpansionStateManager;
+    private final ShadeExpansionStateManager mShadeExpansionStateManager;
 
     private boolean mIsTrackingBarGesture = false;
 
@@ -99,7 +103,7 @@
             NotificationShadeDepthController depthController,
             NotificationShadeWindowView notificationShadeWindowView,
             NotificationPanelViewController notificationPanelViewController,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             StatusBarWindowStateController statusBarWindowStateController,
@@ -108,7 +112,10 @@
             NotificationShadeWindowController controller,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             AmbientState ambientState,
-            PulsingGestureListener pulsingGestureListener
+            PulsingGestureListener pulsingGestureListener,
+            FeatureFlags featureFlags,
+            KeyguardBouncerViewModel keyguardBouncerViewModel,
+            KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory
     ) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
@@ -116,7 +123,7 @@
         mView = notificationShadeWindowView;
         mDockManager = dockManager;
         mNotificationPanelViewController = notificationPanelViewController;
-        mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeExpansionStateManager = shadeExpansionStateManager;
         mDepthController = depthController;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -130,6 +137,12 @@
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
+        if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
+            KeyguardBouncerViewBinder.bind(
+                    mView.findViewById(R.id.keyguard_bouncer_container),
+                    keyguardBouncerViewModel,
+                    keyguardBouncerComponentFactory);
+        }
     }
 
     /**
@@ -390,7 +403,7 @@
         setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper());
 
         mDepthController.setRoot(mView);
-        mPanelExpansionStateManager.addExpansionListener(mDepthController);
+        mShadeExpansionStateManager.addExpansionListener(mDepthController);
     }
 
     public NotificationShadeWindowView getView() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index c3f1e57..fa51d85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -67,7 +67,6 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -96,6 +95,7 @@
     private float mMinExpandHeight;
     private boolean mPanelUpdateWhenAnimatorEnds;
     private final boolean mVibrateOnOpening;
+    private boolean mHasVibratedOnOpen = false;
     protected boolean mIsLaunchAnimationRunning;
     private int mFixedDuration = NO_FIXED_DURATION;
     protected float mOverExpansion;
@@ -197,7 +197,7 @@
     protected final SysuiStatusBarStateController mStatusBarStateController;
     protected final AmbientState mAmbientState;
     protected final LockscreenGestureLogger mLockscreenGestureLogger;
-    private final PanelExpansionStateManager mPanelExpansionStateManager;
+    private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final InteractionJankMonitor mInteractionJankMonitor;
     protected final SystemClock mSystemClock;
 
@@ -240,7 +240,7 @@
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             LockscreenGestureLogger lockscreenGestureLogger,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             AmbientState ambientState,
             InteractionJankMonitor interactionJankMonitor,
             ShadeLogger shadeLogger,
@@ -255,7 +255,7 @@
         mView = view;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLockscreenGestureLogger = lockscreenGestureLogger;
-        mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeLog = shadeLogger;
         TouchHandler touchHandler = createTouchHandler();
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@@ -353,8 +353,8 @@
 
     private void startOpening(MotionEvent event) {
         updatePanelExpansionAndVisibility();
-        maybeVibrateOnOpening();
-
+        // Reset at start so haptic can be triggered as soon as panel starts to open.
+        mHasVibratedOnOpen = false;
         //TODO: keyguard opens QS a different way; log that too?
 
         // Log the position of the swipe that opened the panel
@@ -368,9 +368,18 @@
                 .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
     }
 
-    protected void maybeVibrateOnOpening() {
+    /**
+     * Maybe vibrate as panel is opened.
+     *
+     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
+     * being opened programmatically (such as by the open panel gesture), we always play haptic.
+     */
+    protected void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+            if (!openingWithTouch || !mHasVibratedOnOpen) {
+                mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+                mHasVibratedOnOpen = true;
+            }
         }
     }
 
@@ -1101,7 +1110,7 @@
      *   {@link #updateVisibility()}? That would allow us to make this method private.
      */
     public void updatePanelExpansionAndVisibility() {
-        mPanelExpansionStateManager.onPanelExpansionChanged(
+        mShadeExpansionStateManager.onPanelExpansionChanged(
                 mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
         updateVisibility();
     }
@@ -1371,6 +1380,9 @@
                     break;
                 case MotionEvent.ACTION_MOVE:
                     addMovement(event);
+                    if (!isFullyCollapsed()) {
+                        maybeVibrateOnOpening(true /* openingWithTouch */);
+                    }
                     float h = y - mInitialExpandY;
 
                     // If the panel was collapsed when touching, we only need to check for the
@@ -1475,7 +1487,7 @@
         return mExpandedFraction;
     }
 
-    protected PanelExpansionStateManager getPanelExpansionStateManager() {
-        return mPanelExpansionStateManager;
+    protected ShadeExpansionStateManager getPanelExpansionStateManager() {
+        return mShadeExpansionStateManager;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
index 7c61b29..71dfafa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
 
 import android.annotation.FloatRange
 
-data class PanelExpansionChangeEvent(
+data class ShadeExpansionChangeEvent(
     /** 0 when collapsed, 1 when fully expanded. */
     @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
     /** Whether the panel should be considered expanded */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
index d003824..a5a9ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionListener.kt
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
 
 /** A listener interface to be notified of expansion events for the notification panel. */
-fun interface PanelExpansionListener {
+fun interface ShadeExpansionListener {
     /**
      * Invoked whenever the notification panel expansion changes, at every animation frame. This is
      * the main expansion that happens when the user is swiping up to dismiss the lock screen and
      * swiping to pull down the notification shade.
      */
-    fun onPanelExpansionChanged(event: PanelExpansionChangeEvent)
+    fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 6b7c42e..f617d47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
 
 import android.annotation.IntDef
 import android.util.Log
@@ -29,10 +29,10 @@
  * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
  */
 @SysUISingleton
-class PanelExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() {
 
-    private val expansionListeners = mutableListOf<PanelExpansionListener>()
-    private val stateListeners = mutableListOf<PanelStateListener>()
+    private val expansionListeners = mutableListOf<ShadeExpansionListener>()
+    private val stateListeners = mutableListOf<ShadeStateListener>()
 
     @PanelState private var state: Int = STATE_CLOSED
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -45,24 +45,25 @@
      *
      * Listener will also be immediately notified with the current values.
      */
-    fun addExpansionListener(listener: PanelExpansionListener) {
+    fun addExpansionListener(listener: ShadeExpansionListener) {
         expansionListeners.add(listener)
         listener.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount))
+            ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+        )
     }
 
     /** Removes an expansion listener. */
-    fun removeExpansionListener(listener: PanelExpansionListener) {
+    fun removeExpansionListener(listener: ShadeExpansionListener) {
         expansionListeners.remove(listener)
     }
 
     /** Adds a listener that will be notified when the panel state has changed. */
-    fun addStateListener(listener: PanelStateListener) {
+    fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
     }
 
     /** Removes a state listener. */
-    fun removeStateListener(listener: PanelStateListener) {
+    fun removeStateListener(listener: ShadeStateListener) {
         stateListeners.remove(listener)
     }
 
@@ -110,25 +111,26 @@
 
         debugLog(
             "panelExpansionChanged:" +
-                    "start state=${oldState.panelStateToString()} " +
-                    "end state=${state.panelStateToString()} " +
-                    "f=$fraction " +
-                    "expanded=$expanded " +
-                    "tracking=$tracking " +
-                    "dragDownPxAmount=$dragDownPxAmount " +
-                    "${if (fullyOpened) " fullyOpened" else ""} " +
-                    if (fullyClosed) " fullyClosed" else ""
+                "start state=${oldState.panelStateToString()} " +
+                "end state=${state.panelStateToString()} " +
+                "f=$fraction " +
+                "expanded=$expanded " +
+                "tracking=$tracking " +
+                "dragDownPxAmount=$dragDownPxAmount " +
+                "${if (fullyOpened) " fullyOpened" else ""} " +
+                if (fullyClosed) " fullyClosed" else ""
         )
 
         val expansionChangeEvent =
-            PanelExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+            ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
         expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
     }
 
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
         debugLog(
-            "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
+            "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}"
+        )
         if (this.state != state) {
             updateStateInternal(state)
         }
@@ -165,5 +167,5 @@
     }
 }
 
-private val TAG = PanelExpansionStateManager::class.simpleName
+private val TAG = ShadeExpansionStateManager::class.simpleName
 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
index ca667dd..74468a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateListener.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.panelstate
+package com.android.systemui.shade
 
 /** A listener interface to be notified of state change events for the notification panel. */
-fun interface PanelStateListener {
-    /** Called when the panel's expansion state has changed.   */
+fun interface ShadeStateListener {
+    /** Called when the panel's expansion state has changed. */
     fun onPanelStateChanged(@PanelState state: Int)
 }
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 618c892..a77c21a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -7,12 +7,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+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.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.LargeScreenUtils
@@ -35,7 +35,7 @@
     private var inSplitShade = false
     private var splitShadeScrimTransitionDistance = 0
     private var lastExpansionFraction: Float? = null
-    private var lastExpansionEvent: PanelExpansionChangeEvent? = null
+    private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
     private var currentPanelState: Int? = null
 
     init {
@@ -61,8 +61,8 @@
         onStateChanged()
     }
 
-    fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
-        lastExpansionEvent = panelExpansionChangeEvent
+    fun onPanelExpansionChanged(shadeExpansionChangeEvent: ShadeExpansionChangeEvent) {
+        lastExpansionEvent = shadeExpansionChangeEvent
         onStateChanged()
     }
 
@@ -75,7 +75,7 @@
     }
 
     private fun calculateScrimExpansionFraction(
-        expansionEvent: PanelExpansionChangeEvent,
+        expansionEvent: ShadeExpansionChangeEvent,
         @PanelState panelState: Int?
     ): Float {
         return if (canUseCustomFraction(panelState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index 6c3a028..22e847d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.shade.transition
 
-import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.shade.PanelState
 
 /** Represents an over scroller for the non-lockscreen shade. */
 interface ShadeOverScroller {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 58acfb4..1e8208f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -7,13 +7,13 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.PanelState
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.panelStateToString
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.panelStateToString
 import com.android.systemui.statusbar.policy.ConfigurationController
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -24,7 +24,7 @@
 @Inject
 constructor(
     configurationController: ConfigurationController,
-    panelExpansionStateManager: PanelExpansionStateManager,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
     dumpManager: DumpManager,
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
@@ -39,7 +39,7 @@
 
     private var inSplitShade = false
     private var currentPanelState: Int? = null
-    private var lastPanelExpansionChangeEvent: PanelExpansionChangeEvent? = null
+    private var lastShadeExpansionChangeEvent: ShadeExpansionChangeEvent? = null
 
     private val splitShadeOverScroller by lazy {
         splitShadeOverScrollerFactory.create({ qs }, { notificationStackScrollLayoutController })
@@ -60,8 +60,8 @@
                     updateResources()
                 }
             })
-        panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
-        panelExpansionStateManager.addStateListener(this::onPanelStateChanged)
+        shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+        shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
         dumpManager.registerDumpable("ShadeTransitionController") { printWriter, _ ->
             dump(printWriter)
         }
@@ -77,8 +77,8 @@
         scrimShadeTransitionController.onPanelStateChanged(state)
     }
 
-    private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
-        lastPanelExpansionChangeEvent = event
+    private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+        lastShadeExpansionChangeEvent = event
         shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
         scrimShadeTransitionController.onPanelExpansionChanged(event)
     }
@@ -95,7 +95,7 @@
                 inSplitShade: $inSplitShade
                 isScreenUnlocked: ${isScreenUnlocked()}
                 currentPanelState: ${currentPanelState?.panelStateToString()}
-                lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
+                lastPanelExpansionChangeEvent: $lastShadeExpansionChangeEvent
                 qs.isInitialized: ${this::qs.isInitialized}
                 npvc.isInitialized: ${this::notificationPanelViewController.isInitialized}
                 nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 204dd3c..8c57194 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -10,11 +10,11 @@
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.PanelState
+import com.android.systemui.shade.STATE_CLOSED
+import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelState
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -37,6 +37,7 @@
     private var previousOverscrollAmount = 0
     private var dragDownAmount: Float = 0f
     @PanelState private var panelState: Int = STATE_CLOSED
+
     private var releaseOverScrollAnimator: Animator? = null
 
     private val qS: QS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064..ce730ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@
 import android.widget.FrameLayout
 
 /**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * 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]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
  */
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index e06c977..073ab8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1199,7 +1199,8 @@
             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED);
+                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
+                    || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
         }
 
         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8699441..c290ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -42,7 +42,6 @@
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -97,6 +96,7 @@
     private final List<UserChangedListener> mListeners = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
+    private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
 
     private boolean mShowLockscreenNotifications;
     private boolean mAllowLockscreenRemoteInput;
@@ -157,7 +157,7 @@
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
                     // Start the overview connection to the launcher service
-                    Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
                     break;
                 case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
                     final IntentSender intentSender = intent.getParcelableExtra(
@@ -199,6 +199,7 @@
             Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
             Lazy<CommonNotifCollection> commonNotifCollectionLazy,
             NotificationClickNotifier clickNotifier,
+            Lazy<OverviewProxyService> overviewProxyServiceLazy,
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
@@ -214,6 +215,7 @@
         mVisibilityProviderLazy = visibilityProviderLazy;
         mCommonNotifCollectionLazy = commonNotifCollectionLazy;
         mClickNotifier = clickNotifier;
+        mOverviewProxyServiceLazy = overviewProxyServiceLazy;
         statusBarStateController.addCallback(this);
         mLockPatternUtils = new LockPatternUtils(context);
         mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c900c5a..4be5a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -43,7 +43,6 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -89,11 +88,9 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final StatusBarStateController mStatusBarStateController
-            = Dependency.get(StatusBarStateController.class);
-    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
-    private final KeyguardStateController mKeyguardStateController = Dependency.get(
-            KeyguardStateController.class);
+    private final StatusBarStateController mStatusBarStateController;
+    private final SysuiColorExtractor mColorExtractor;
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
@@ -179,6 +176,9 @@
             NotifCollection notifCollection,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor,
+            KeyguardStateController keyguardStateController,
             DumpManager dumpManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
@@ -192,6 +192,9 @@
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
+        mStatusBarStateController = statusBarStateController;
+        mColorExtractor = colorExtractor;
+        mKeyguardStateController = keyguardStateController;
 
         setupNotifPipeline();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index cb13fcf..b5879ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,12 +38,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.LargeScreenUtils
@@ -69,7 +69,7 @@
     private val context: Context,
     dumpManager: DumpManager,
     configurationController: ConfigurationController
-) : PanelExpansionListener, Dumpable {
+) : ShadeExpansionListener, Dumpable {
     companion object {
         private const val WAKE_UP_ANIMATION_ENABLED = true
         private const val VELOCITY_SCALE = 100f
@@ -338,7 +338,7 @@
     /**
      * Update blurs when pulling down the shade
      */
-    override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+    override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
         val rawFraction = event.fraction
         val tracking = event.tracking
         val timestamp = SystemClock.elapsedRealtimeNanos()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 41c0367..d67f94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -168,6 +168,17 @@
         return new ShelfState();
     }
 
+    @Override
+    public String toString() {
+        return "NotificationShelf("
+                + "hideBackground=" + mHideBackground + " notGoneIndex=" + mNotGoneIndex
+                + " hasItemsInStableShelf=" + mHasItemsInStableShelf
+                + " statusBarState=" + mStatusBarState + " interactive=" + mInteractive
+                + " animationsEnabled=" + mAnimationsEnabled
+                + " showNotificationShelf=" + mShowNotificationShelf
+                + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
+    }
+
     /** Update the state of the shelf. */
     public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
             AmbientState ambientState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 039a362..c35c5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
@@ -36,7 +37,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.os.Parcelable;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -60,6 +60,8 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.util.drawable.DrawableSize;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -87,6 +89,10 @@
     public static final int STATE_DOT = 1;
     public static final int STATE_HIDDEN = 2;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
+    public @interface VisibleState { }
+
     private static final String TAG = "StatusBarIconView";
     private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
             = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -134,6 +140,7 @@
     private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private float mDotRadius;
     private int mStaticDotRadius;
+    @StatusBarIconView.VisibleState
     private int mVisibleState = STATE_ICON;
     private float mIconAppearAmount = 1.0f;
     private ObjectAnimator mIconAppearAnimator;
@@ -551,9 +558,12 @@
         }
     }
 
+    @Override
     public String toString() {
-        return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
-            + " notification=" + mNotification + ")";
+        return "StatusBarIconView("
+                + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+                + " iconColor=#" + Integer.toHexString(mIconColor)
+                + " notification=" + mNotification + ')';
     }
 
     public StatusBarNotification getNotification() {
@@ -744,11 +754,12 @@
     }
 
     @Override
-    public void setVisibleState(int state) {
+    public void setVisibleState(@StatusBarIconView.VisibleState int state) {
         setVisibleState(state, true /* animate */, null /* endRunnable */);
     }
 
-    public void setVisibleState(int state, boolean animate) {
+    @Override
+    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
         setVisibleState(state, animate, null);
     }
 
@@ -860,6 +871,7 @@
         return mIconAppearAmount;
     }
 
+    @StatusBarIconView.VisibleState
     public int getVisibleState() {
         return mVisibleState;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 25c6dce..fdad101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
@@ -43,7 +42,10 @@
 
 import java.util.ArrayList;
 
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
         StatusIconDisplayable {
     private static final String TAG = "StatusBarMobileView";
 
@@ -59,7 +61,8 @@
     private ImageView mOut;
     private ImageView mMobile, mMobileType, mMobileRoaming;
     private View mMobileRoamingSpace;
-    private int mVisibleState = -1;
+    @StatusBarIconView.VisibleState
+    private int mVisibleState = STATE_HIDDEN;
     private DualToneHandler mDualToneHandler;
     private boolean mForceHidden;
 
@@ -100,11 +103,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     @Override
     public void getDrawingRect(Rect outRect) {
         super.getDrawingRect(outRect);
@@ -271,7 +269,7 @@
     }
 
     @Override
-    public void setVisibleState(int state, boolean animate) {
+    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
         if (state == mVisibleState) {
             return;
         }
@@ -312,6 +310,7 @@
     }
 
     @Override
+    @StatusBarIconView.VisibleState
     public int getVisibleState() {
         return mVisibleState;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 5aee62e..decc70d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@
 /**
  * Start small: StatusBarWifiView will be able to layout from a WifiIconState
  */
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
     private static final String TAG = "StatusBarWifiView";
 
     /// Used to show etc dots
@@ -55,7 +55,8 @@
     private View mAirplaneSpacer;
     private WifiIconState mState;
     private String mSlot;
-    private int mVisibleState = -1;
+    @StatusBarIconView.VisibleState
+    private int mVisibleState = STATE_HIDDEN;
 
     public static StatusBarWifiView fromContext(Context context, String slot) {
         LayoutInflater inflater = LayoutInflater.from(context);
@@ -107,7 +108,7 @@
     }
 
     @Override
-    public void setVisibleState(int state, boolean animate) {
+    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
         if (state == mVisibleState) {
             return;
         }
@@ -131,6 +132,7 @@
     }
 
     @Override
+    @StatusBarIconView.VisibleState
     public int getVisibleState() {
         return mVisibleState;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index d541fae..1196211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -22,14 +22,32 @@
     String getSlot();
     void setStaticDrawableColor(int color);
     void setDecorColor(int color);
-    default void setVisibleState(int state) {
+
+    /** Sets the visible state that this displayable should be. */
+    default void setVisibleState(@StatusBarIconView.VisibleState int state) {
         setVisibleState(state, false);
     }
-    void setVisibleState(int state, boolean animate);
+
+    /**
+     * Sets the visible state that this displayable should be, and whether the change should
+     * animate.
+     */
+    void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate);
+
+    /** Returns the current visible state of this displayable. */
+    @StatusBarIconView.VisibleState
     int getVisibleState();
+
+    /**
+     * Returns true if this icon should be visible if there's space, and false otherwise.
+     *
+     * Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
+     * more icons than space, in which case this icon might just show a dot or might be completely
+     * hidden. {@link #getVisibleState} will return the icon's actual visible status.
+     */
     boolean isIconVisible();
+
     default boolean isIconBlocked() {
         return false;
     }
 }
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
deleted file mode 100644
index 4551807..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ /dev/null
@@ -1,62 +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.systemui.statusbar;
-
-import android.content.Context;
-import android.content.DialogInterface;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-
-public class UserUtil {
-    public static void deleteUserWithPrompt(Context context, int userId,
-                                            UserSwitcherController userSwitcherController) {
-        new RemoveUserDialog(context, userId, userSwitcherController).show();
-    }
-
-    private final static class RemoveUserDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
-
-        private final int mUserId;
-        private final UserSwitcherController mUserSwitcherController;
-
-        public RemoveUserDialog(Context context, int userId,
-                                UserSwitcherController userSwitcherController) {
-            super(context);
-            setTitle(R.string.user_remove_user_title);
-            setMessage(context.getString(R.string.user_remove_user_message));
-            setButton(DialogInterface.BUTTON_NEUTRAL,
-                    context.getString(android.R.string.cancel), this);
-            setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(R.string.user_remove_user_remove), this);
-            setCanceledOnTouchOutside(false);
-            mUserId = userId;
-            mUserSwitcherController = userSwitcherController;
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            if (which == BUTTON_NEUTRAL) {
-                cancel();
-            } else {
-                dismiss();
-                mUserSwitcherController.removeUserId(mUserId);
-            }
-        }
-    }
-}
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 7cd79ca..11e3d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,6 +26,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -130,6 +131,9 @@
             NotifCollection notifCollection,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor,
+            KeyguardStateController keyguardStateController,
             DumpManager dumpManager) {
         return new NotificationMediaManager(
                 context,
@@ -142,6 +146,9 @@
                 notifCollection,
                 mainExecutor,
                 mediaDataManager,
+                statusBarStateController,
+                colorExtractor,
+                keyguardStateController,
                 dumpManager);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 126a986..7242506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -28,8 +30,6 @@
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import javax.inject.Inject
@@ -42,7 +42,7 @@
     private val bypassController: KeyguardBypassController,
     private val dozeParameters: DozeParameters,
     private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener {
 
     private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
         "notificationVisibility") {
@@ -293,7 +293,7 @@
         this.state = newState
     }
 
-    override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+    override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
         val collapsedEnough = event.fraction <= 0.9f
         if (collapsedEnough != this.collapsedEnoughToHide) {
             val couldShowPulsingHuns = canShowPulsingHuns
@@ -426,4 +426,4 @@
          */
         @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3eaa988..e129ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1398,7 +1398,7 @@
             throw exception;
         }
 
-        Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+        Log.wtf(TAG, "Allowing " + mConsecutiveReentrantRebuilds
                 + " consecutive reentrant notification pipeline rebuild(s).", exception);
         mChoreographer.schedule();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8278b54..ccf6fec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -393,7 +393,7 @@
             val posted = mPostedEntries.compute(entry.key) { _, value ->
                 value?.also { update ->
                     update.wasUpdated = true
-                    update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+                    update.shouldHeadsUpEver = shouldHeadsUpEver
                     update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
                     update.isAlerting = isAlerting
                     update.isBinding = isBinding
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 801e544..8eef3f3 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
@@ -19,6 +19,8 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
@@ -38,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
+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
@@ -71,6 +74,8 @@
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
     private val fgsNotifListener: ForegroundServiceNotificationListener,
+    private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
+    private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
     override fun initialize(
@@ -112,6 +117,9 @@
         notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
         fgsNotifListener.init()
+        if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
+            memoryMonitor.get().init()
+        }
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
new file mode 100644
index 0000000..832a739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+/** Describes usage of a notification. */
+data class NotificationMemoryUsage(
+    val packageName: String,
+    val notificationId: String,
+    val objectUsage: NotificationObjectUsage,
+)
+
+/**
+ * Describes current memory usage of a [android.app.Notification] object.
+ *
+ * The values are in bytes.
+ */
+data class NotificationObjectUsage(
+    val smallIcon: Int,
+    val largeIcon: Int,
+    val extras: Int,
+    val style: String?,
+    val styleIcon: Int,
+    val bigPicture: Int,
+    val extender: Int,
+    val hasCustomView: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
new file mode 100644
index 0000000..ef7fa33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -0,0 +1,239 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.core.util.contains
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** This class monitors and logs current Notification memory use. */
+@SysUISingleton
+class NotificationMemoryMonitor
+@Inject
+constructor(
+    val notificationPipeline: NotifPipeline,
+    val dumpManager: DumpManager,
+) : Dumpable {
+
+    companion object {
+        private const val TAG = "NotificationMemMonitor"
+        private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+        private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+        private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+        private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+        private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+    }
+
+    fun init() {
+        Log.d(TAG, "NotificationMemoryMonitor initialized.")
+        dumpManager.registerDumpable(javaClass.simpleName, this)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+    }
+
+    @WorkerThread
+    fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
+        return notificationMemoryUse(notificationPipeline.allNotifs)
+    }
+
+    /** Returns a list of memory use entries for currently shown notifications. */
+    @WorkerThread
+    fun notificationMemoryUse(
+        notifications: Collection<NotificationEntry>
+    ): List<NotificationMemoryUsage> {
+        return notifications.asSequence().map { entry ->
+            val packageName = entry.sbn.packageName
+            val notificationObjectUsage =
+                computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
+            NotificationMemoryUsage(
+                packageName,
+                NotificationUtils.logKey(entry.sbn.key),
+                notificationObjectUsage)
+        }.toList()
+    }
+
+    /**
+     * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+     * inspect Bitmaps in the object and provide summary of memory usage.
+     */
+    private fun computeNotificationObjectUse(
+        notification: Notification,
+        seenBitmaps: HashSet<Int>
+    ): NotificationObjectUsage {
+        val extras = notification.extras
+        val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+        val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+        // Collect memory usage of extra styles
+
+        // Big Picture
+        val bigPictureIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
+                computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+        val bigPictureUse =
+            computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+                computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+        // People
+        val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+        val peopleUse =
+            peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+        // Calling
+        val callingPersonUse =
+            computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+        val verificationIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+        // Messages
+        val messages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+            )
+        val messagesUse =
+            messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+        val historicMessages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+            )
+        val historyicMessagesUse =
+            historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+        // Extenders
+        val carExtender = extras.getBundle(CAR_EXTENSIONS)
+        val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+        val carExtenderIcon =
+            computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+        val tvExtender = extras.getBundle(TV_EXTENSIONS)
+        val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+        val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+        val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+        val wearExtenderBackground =
+            computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+        val style = notification.notificationStyle
+        val hasCustomView = notification.contentView != null || notification.bigContentView != null
+        val extrasSize = computeBundleSize(extras)
+
+        return NotificationObjectUsage(
+            smallIconUse,
+            largeIconUse,
+            extrasSize,
+            style?.simpleName,
+            bigPictureIconUse +
+                peopleUse +
+                callingPersonUse +
+                verificationIconUse +
+                messagesUse +
+                historyicMessagesUse,
+            bigPictureUse,
+            carExtenderSize +
+                carExtenderIcon +
+                tvExtenderSize +
+                wearExtenderSize +
+                wearExtenderBackground,
+            hasCustomView
+        )
+    }
+
+    /**
+     * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+     * bitmaps). Can be slow.
+     */
+    private fun computeBundleSize(extras: Bundle): Int {
+        val parcel = Parcel.obtain()
+        try {
+            extras.writeToParcel(parcel, 0)
+            return parcel.dataSize()
+        } finally {
+            parcel.recycle()
+        }
+    }
+
+    /**
+     * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+     * if the key does not exist in extras.
+     */
+    private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+        return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+            is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+            is Icon -> computeIconUse(parcelable, seenBitmaps)
+            is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+            else -> 0
+        }
+    }
+
+    /**
+     * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+     * defined via Uri or a resource.
+     *
+     * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+     */
+    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+        when (icon?.type) {
+            Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+            else -> 0
+        }
+
+    /**
+     * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+     * seenBitmaps set, this method returns 0 to avoid double counting.
+     *
+     * @return memory usage of the bitmap in bytes
+     */
+    private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+        val refId = System.identityHashCode(bitmap)
+        if (seenBitmaps?.contains(refId) == true) {
+            return 0
+        }
+
+        seenBitmaps?.add(refId)
+        return bitmap.allocationByteCount
+    }
+
+    private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+        val refId = System.identityHashCode(icon.dataBytes)
+        if (seenBitmaps.contains(refId)) {
+            return 0
+        }
+
+        seenBitmaps.add(refId)
+        return icon.dataLength
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 9faef1b..5ca13c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -45,11 +45,21 @@
     void logPanelShown(boolean isLockscreen,
             @Nullable List<NotificationEntry> visibleNotifications);
 
+    /**
+     * Log a NOTIFICATION_PANEL_REPORTED statsd event, with
+     * {@link NotificationPanelEvent#NOTIFICATION_DRAG} as the eventID.
+     *
+     * @param draggedNotification the notification that is being dragged
+     */
+    void logNotificationDrag(NotificationEntry draggedNotification);
+
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "Notification panel shown from status bar.")
         NOTIFICATION_PANEL_OPEN_STATUS_BAR(200),
         @UiEvent(doc = "Notification panel shown from lockscreen.")
-        NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201);
+        NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201),
+        @UiEvent(doc = "Notification was dragged")
+        NOTIFICATION_DRAG(1226);
 
         private final int mId;
         NotificationPanelEvent(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 75a6019..9a63228 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
+import static com.android.systemui.statusbar.notification.logging.NotificationPanelLogger.NotificationPanelEvent.NOTIFICATION_DRAG;
+
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 
 import com.google.protobuf.nano.MessageNano;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -38,4 +41,14 @@
                 /* int num_notifications*/ proto.notifications.length,
                 /* byte[] notifications*/ MessageNano.toByteArray(proto));
     }
+
+    @Override
+    public void logNotificationDrag(NotificationEntry draggedNotification) {
+        final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
+                Collections.singletonList(draggedNotification));
+        SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+                /* int event_id */ NOTIFICATION_DRAG.getId(),
+                /* int num_notifications*/ proto.notifications.length,
+                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+    }
 }
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 4939a9c..64f87ca 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
@@ -45,12 +45,17 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
+import java.util.Collections;
+
 import javax.inject.Inject;
 
 /**
@@ -63,14 +68,17 @@
     private final Context mContext;
     private final HeadsUpManager mHeadsUpManager;
     private final ShadeController mShadeController;
+    private NotificationPanelLogger mNotificationPanelLogger;
 
     @Inject
     public ExpandableNotificationRowDragController(Context context,
             HeadsUpManager headsUpManager,
-            ShadeController shadeController) {
+            ShadeController shadeController,
+            NotificationPanelLogger notificationPanelLogger) {
         mContext = context;
         mHeadsUpManager = headsUpManager;
         mShadeController = shadeController;
+        mNotificationPanelLogger = notificationPanelLogger;
 
         init();
     }
@@ -120,12 +128,16 @@
         dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent);
         dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
         ClipData.Item item = new ClipData.Item(dragIntent);
+        InstanceId instanceId = new InstanceIdSequence(Integer.MAX_VALUE).newInstanceId();
+        item.getIntent().putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, instanceId);
         ClipData dragData = new ClipData(clipDescription, item);
         View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
         view.setOnDragListener(getDraggedViewDragListener());
         boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL
                 | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
         if (result) {
+            // Log notification drag only if it succeeds
+            mNotificationPanelLogger.logNotificationDrag(enr.getEntry());
             view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
             if (enr.isPinned()) {
                 mHeadsUpManager.releaseAllImmediately();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index bc172ce..0b435fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -35,12 +35,12 @@
  * bounds change.
  */
 public class NotificationSection {
-    private @PriorityBucket int mBucket;
-    private View mOwningView;
-    private Rect mBounds = new Rect();
-    private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
-    private Rect mStartAnimationRect = new Rect();
-    private Rect mEndAnimationRect = new Rect();
+    private @PriorityBucket final int mBucket;
+    private final View mOwningView;
+    private final Rect mBounds = new Rect();
+    private final Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
+    private final Rect mStartAnimationRect = new Rect();
+    private final Rect mEndAnimationRect = new Rect();
     private ObjectAnimator mTopAnimator = null;
     private ObjectAnimator mBottomAnimator = null;
     private ExpandableView mFirstVisibleChild;
@@ -277,7 +277,6 @@
                 }
             }
         }
-        top = Math.max(minTopPosition, top);
         ExpandableView lastView = getLastVisibleChild();
         if (lastView != null) {
             float finalTranslationY = ViewState.getFinalTranslationY(lastView);
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 5fbaa51..6821b14 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
@@ -135,7 +135,7 @@
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
 
     // Delay in milli-seconds before shade closes for clear all.
-    private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+    private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
     private boolean mShadeNeedsToClose = false;
 
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
@@ -152,7 +152,7 @@
     private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
     private boolean mKeyguardBypassEnabled;
 
-    private ExpandHelper mExpandHelper;
+    private final ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
@@ -165,12 +165,7 @@
 
     private VelocityTracker mVelocityTracker;
     private OverScroller mScroller;
-    /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
-    private int mLastScrollerY;
-    /**
-     * True if the max position was set to a known position on the last call to {@link #mScroller}.
-     */
-    private boolean mIsScrollerBoundSet;
+
     private Runnable mFinishScrollingCallback;
     private int mTouchSlop;
     private float mSlopMultiplier;
@@ -194,7 +189,6 @@
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
-    private int mCollapsedSize;
     private int mPaddingBetweenElements;
     private int mMaxTopPadding;
     private int mTopPadding;
@@ -210,15 +204,15 @@
     private final StackScrollAlgorithm mStackScrollAlgorithm;
     private final AmbientState mAmbientState;
 
-    private GroupMembershipManager mGroupMembershipManager;
-    private GroupExpansionManager mGroupExpansionManager;
-    private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
-    private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
-    private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
-    private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
-    private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
-    private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
-    private ArrayList<View> mSwipedOutViews = new ArrayList<>();
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final GroupExpansionManager mGroupExpansionManager;
+    private final HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
+    private final ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+    private final ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
+    private final ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
+    private final HashSet<View> mFromMoreCardAdditions = new HashSet<>();
+    private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+    private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
     private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
     private boolean mAnimationsEnabled;
@@ -296,7 +290,7 @@
     private boolean mDisallowDismissInThisMotion;
     private boolean mDisallowScrollingInThisMotion;
     private long mGoToFullShadeDelay;
-    private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
+    private final ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
@@ -309,17 +303,16 @@
     };
     private NotificationStackScrollLogger mLogger;
     private CentralSurfaces mCentralSurfaces;
-    private int[] mTempInt2 = new int[2];
+    private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
-    private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
-    private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
-    private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
+    private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
+    private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
+    private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
             = new HashSet<>();
-    private boolean mTrackingHeadsUp;
     private boolean mForceNoOverlappingRendering;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
     private boolean mAnimationRunning;
-    private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
+    private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
@@ -327,21 +320,21 @@
             return true;
         }
     };
-    private NotificationSection[] mSections;
+    private final NotificationSection[] mSections;
     private boolean mAnimateNextBackgroundTop;
     private boolean mAnimateNextBackgroundBottom;
     private boolean mAnimateNextSectionBoundsChange;
     private int mBgColor;
     private float mDimAmount;
     private ValueAnimator mDimAnimator;
-    private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
+    private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
     private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
             mDimAnimator = null;
         }
     };
-    private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
+    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
             = new ValueAnimator.AnimatorUpdateListener() {
 
         @Override
@@ -351,29 +344,23 @@
     };
     protected ViewGroup mQsHeader;
     // Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
-    private Rect mQsHeaderBound = new Rect();
+    private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
-    private ViewTreeObserver.OnPreDrawListener mShadowUpdater
+    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
             = () -> {
                 updateViewShadows();
                 return true;
             };
-    private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
+    private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
                 updateBackground();
                 return true;
             };
-    private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
+    private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
-        if (endY < otherEndY) {
-            return -1;
-        } else if (endY > otherEndY) {
-            return 1;
-        } else {
-            // The two notifications end at the same location
-            return 0;
-        }
+        // Return zero when the two notifications end at the same location
+        return Float.compare(endY, otherEndY);
     };
     private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
         @Override
@@ -435,16 +422,14 @@
     private int mUpcomingStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
-    private Runnable mReflingAndAnimateScroll = () -> {
-        animateScroll();
-    };
+    private final Runnable mReflingAndAnimateScroll = this::animateScroll;
     private int mCornerRadius;
     private int mMinimumPaddings;
     private int mQsTilePadding;
     private boolean mSkinnyNotifsInLandscape;
     private int mSidePaddings;
     private final Rect mBackgroundAnimationRect = new Rect();
-    private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
+    private final ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
     private int mHeadsUpInset;
 
     /**
@@ -479,8 +464,6 @@
     private int mWaterfallTopInset;
     private NotificationStackScrollLayoutController mController;
 
-    private boolean mKeyguardMediaControllorVisible;
-
     /**
      * The clip path used to clip the view in a rounded way.
      */
@@ -501,7 +484,7 @@
     private int mRoundedRectClippingTop;
     private int mRoundedRectClippingBottom;
     private int mRoundedRectClippingRight;
-    private float[] mBgCornerRadii = new float[8];
+    private final float[] mBgCornerRadii = new float[8];
 
     /**
      * Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -527,7 +510,7 @@
     /**
      * Corner radii of the launched notification if it's clipped
      */
-    private float[] mLaunchedNotificationRadii = new float[8];
+    private final float[] mLaunchedNotificationRadii = new float[8];
 
     /**
      * The notification that is being launched currently.
@@ -779,7 +762,7 @@
         y = getLayoutHeight();
         drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y);
 
-        y = (int) mMaxLayoutHeight;
+        y = mMaxLayoutHeight;
         drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight = " + y);
 
         // The space between mTopPadding and mKeyguardBottomPadding determines the available space
@@ -997,7 +980,6 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
 
         Resources res = context.getResources();
-        mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mAmbientState.reload(context);
@@ -1256,12 +1238,9 @@
     private void clampScrollPosition() {
         int scrollRange = getScrollRange();
         if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
-            boolean animateStackY = false;
-            if (scrollRange < getScrollAmountToScrollBoundary()
-                    && mAnimateStackYForContentHeightChange) {
-                // if the scroll boundary updates the position of the stack,
-                animateStackY = true;
-            }
+            // if the scroll boundary updates the position of the stack,
+            boolean animateStackY = scrollRange < getScrollAmountToScrollBoundary()
+                    && mAnimateStackYForContentHeightChange;
             setOwnScrollY(scrollRange, animateStackY);
         }
     }
@@ -1318,7 +1297,9 @@
                 + mAmbientState.getOverExpansion()
                 - getCurrentOverScrollAmount(false /* top */);
         float fraction = mAmbientState.getExpansionFraction();
-        if (mAmbientState.isBouncerInTransit()) {
+        // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
+        // overlap. Otherwise, we maintain the normal fraction for smoothness.
+        if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
@@ -1504,7 +1485,6 @@
         }
 
         if (mAmbientState.isHiddenAtAll()) {
-            clipToOutline = false;
             invalidateOutline();
             if (isFullyHidden()) {
                 setClipBounds(null);
@@ -1782,7 +1762,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private Runnable mReclamp = new Runnable() {
+    private final Runnable mReclamp = new Runnable() {
         @Override
         public void run() {
             int range = getScrollRange();
@@ -3084,11 +3064,8 @@
         int currentIndex = indexOfChild(child);
 
         if (currentIndex == -1) {
-            boolean isTransient = false;
-            if (child instanceof ExpandableNotificationRow
-                    && child.getTransientContainer() != null) {
-                isTransient = true;
-            }
+            boolean isTransient = child instanceof ExpandableNotificationRow
+                    && child.getTransientContainer() != null;
             Log.e(TAG, "Attempting to re-position "
                     + (isTransient ? "transient" : "")
                     + " view {"
@@ -3149,7 +3126,6 @@
     private void generateHeadsUpAnimationEvents() {
         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
             ExpandableNotificationRow row = eventPair.first;
-            String key = row.getEntry().getKey();
             boolean isHeadsUp = eventPair.second;
             if (isHeadsUp != row.isHeadsUp()) {
                 // For cases where we have a heads up showing and appearing again we shouldn't
@@ -3212,10 +3188,8 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
-        if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
-            return false;
-        }
-        return true;
+        return viewState.yTranslation + viewState.height
+                >= mAmbientState.getMaxHeadsUpTranslation();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4790,7 +4764,6 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
         mAmbientState.setTrackedHeadsUpRow(row);
-        mTrackingHeadsUp = row != null;
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6176,7 +6149,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
+    private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
         @Override
         public ExpandableView getChildAtPosition(float touchX, float touchY) {
             return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
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 1eafaf0..62614d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -181,6 +181,8 @@
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
@@ -219,8 +221,6 @@
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -497,7 +497,7 @@
 
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
-    private final PanelExpansionStateManager mPanelExpansionStateManager;
+    private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
@@ -683,7 +683,7 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
@@ -769,7 +769,7 @@
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
         mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-        mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeExpansionStateManager = shadeExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
         mDisplayMetrics = displayMetrics;
         mMetricsLogger = metricsLogger;
@@ -834,7 +834,7 @@
 
         mScreenOffAnimationController = screenOffAnimationController;
 
-        mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
+        mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
 
         mBubbleExpandListener = (isExpanding, key) ->
                 mContext.getMainExecutor().execute(this::updateScrimController);
@@ -1123,7 +1123,7 @@
         // 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.
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
-        mPanelExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
+        mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
         mUserSwitcherController.init(mNotificationShadeWindowView);
 
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
@@ -1359,7 +1359,7 @@
         }
     }
 
-    private void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+    private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
         float fraction = event.getFraction();
         boolean tracking = event.getTracking();
         dispatchPanelExpansionForKeyguardDismiss(fraction, tracking);
@@ -1544,7 +1544,7 @@
         mKeyguardViewMediator.registerCentralSurfaces(
                 /* statusBar= */ this,
                 mNotificationPanelViewController,
-                mPanelExpansionStateManager,
+                mShadeExpansionStateManager,
                 mBiometricUnlockController,
                 mStackScroller,
                 mKeyguardBypassController);
@@ -3348,7 +3348,8 @@
                 // lock screen where users can use the UDFPS affordance to enter the device
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
-                    && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()) {
+                    && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()
+                    && isKeyguardSecure()) {
                 mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
             }
         }
@@ -4433,10 +4434,11 @@
                     Trace.beginSection("CentralSurfaces#updateDozing");
                     mDozing = isDozing;
 
-                    // Collapse the notification panel if open
                     boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
                             && mDozeParameters.shouldControlScreenOff();
-                    mNotificationPanelViewController.resetViews(dozingAnimated);
+                    // resetting views is already done when going into doze, there's no need to
+                    // reset them again when we're waking up
+                    mNotificationPanelViewController.resetViews(dozingAnimated && isDozing);
 
                     updateQsExpansionEnabled();
                     mKeyguardViewMediator.setDozing(mDozing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 7de4668..0067316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.os.Handler;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
@@ -34,9 +33,6 @@
  */
 @SysUISingleton
 public class DozeScrimController implements StateListener {
-    private static final String TAG = "DozeScrimController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
     private final DozeLog mDozeLog;
     private final DozeParameters mDozeParameters;
     private final Handler mHandler = new Handler();
@@ -44,28 +40,26 @@
     private boolean mDozing;
     private DozeHost.PulseCallback mPulseCallback;
     private int mPulseReason;
-    private boolean mFullyPulsing;
 
     private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
         @Override
         public void onDisplayBlanked() {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
-                        + DozeLog.reasonToString(mPulseReason));
-            }
             if (!mDozing) {
+                mDozeLog.tracePulseDropped("onDisplayBlanked - not dozing");
                 return;
             }
 
-            // Signal that the pulse is ready to turn the screen on and draw.
-            pulseStarted();
+            if (mPulseCallback != null) {
+                // Signal that the pulse is ready to turn the screen on and draw.
+                mDozeLog.tracePulseStart(mPulseReason);
+                mPulseCallback.onPulseStarted();
+            }
         }
 
         @Override
         public void onFinished() {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
-            }
+            mDozeLog.tracePulseEvent("scrimCallback-onFinished", mDozing, mPulseReason);
+
             if (!mDozing) {
                 return;
             }
@@ -78,7 +72,6 @@
                 mHandler.postDelayed(mPulseOutExtended,
                         mDozeParameters.getPulseVisibleDurationExtended());
             }
-            mFullyPulsing = true;
         }
 
         /**
@@ -118,19 +111,14 @@
         }
 
         if (!mDozing || mPulseCallback != null) {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
-                        + (mPulseCallback != null));
-            }
             // Pulse suppressed.
             callback.onPulseFinished();
             if (!mDozing) {
-                mDozeLog.tracePulseDropped("device isn't dozing");
+                mDozeLog.tracePulseDropped("pulse - device isn't dozing");
             } else {
-                mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+                mDozeLog.tracePulseDropped("pulse - already has pulse callback mPulseCallback="
                         + mPulseCallback);
             }
-
             return;
         }
 
@@ -141,9 +129,7 @@
     }
 
     public void pulseOutNow() {
-        if (mPulseCallback != null && mFullyPulsing) {
-            mPulseOut.run();
-        }
+        mPulseOut.run();
     }
 
     public boolean isPulsing() {
@@ -168,24 +154,16 @@
 
     private void cancelPulsing() {
         if (mPulseCallback != null) {
-            if (DEBUG) Log.d(TAG, "Cancel pulsing");
-            mFullyPulsing = false;
+            mDozeLog.tracePulseEvent("cancel", mDozing, mPulseReason);
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
             pulseFinished();
         }
     }
 
-    private void pulseStarted() {
-        mDozeLog.tracePulseStart(mPulseReason);
-        if (mPulseCallback != null) {
-            mPulseCallback.onPulseStarted();
-        }
-    }
-
     private void pulseFinished() {
-        mDozeLog.tracePulseFinish();
         if (mPulseCallback != null) {
+            mDozeLog.tracePulseFinish();
             mPulseCallback.onPulseFinished();
             mPulseCallback = null;
         }
@@ -202,10 +180,9 @@
     private final Runnable mPulseOut = new Runnable() {
         @Override
         public void run() {
-            mFullyPulsing = false;
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
-            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
+            mDozeLog.tracePulseEvent("out", mDozing, mPulseReason);
             if (!mDozing) return;
             pulseFinished();
         }
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 24ce5e9..5196e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -36,7 +36,6 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -48,6 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
@@ -80,7 +80,6 @@
     private final BatteryController mBatteryController;
     private final ScrimController mScrimController;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
-    private final KeyguardViewMediator mKeyguardViewMediator;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final DozeScrimController mDozeScrimController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -95,6 +94,7 @@
     private View mAmbientIndicationContainer;
     private CentralSurfaces mCentralSurfaces;
     private boolean mAlwaysOnSuppressed;
+    private boolean mPulsePending;
 
     @Inject
     public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -104,7 +104,6 @@
             HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
             ScrimController scrimController,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
-            KeyguardViewMediator keyguardViewMediator,
             Lazy<AssistManager> assistManagerLazy,
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
             PulseExpansionHandler pulseExpansionHandler,
@@ -122,7 +121,6 @@
         mBatteryController = batteryController;
         mScrimController = scrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
-        mKeyguardViewMediator = keyguardViewMediator;
         mAssistManagerLazy = assistManagerLazy;
         mDozeScrimController = dozeScrimController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -131,6 +129,7 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -246,7 +245,7 @@
         mDozeScrimController.pulse(new PulseCallback() {
             @Override
             public void onPulseStarted() {
-                callback.onPulseStarted();
+                callback.onPulseStarted(); // requestState(DozeMachine.State.DOZE_PULSING)
                 mCentralSurfaces.updateNotificationPanelTouchState();
                 setPulsing(true);
             }
@@ -254,7 +253,7 @@
             @Override
             public void onPulseFinished() {
                 mPulsing = false;
-                callback.onPulseFinished();
+                callback.onPulseFinished(); // requestState(DozeMachine.State.DOZE_PULSE_DONE)
                 mCentralSurfaces.updateNotificationPanelTouchState();
                 mScrimController.setWakeLockScreenSensorActive(false);
                 setPulsing(false);
@@ -338,9 +337,8 @@
 
     @Override
     public void stopPulsing() {
-        if (mDozeScrimController.isPulsing()) {
-            mDozeScrimController.pulseOutNow();
-        }
+        setPulsePending(false); // prevent any pending pulses from continuing
+        mDozeScrimController.pulseOutNow();
     }
 
     @Override
@@ -451,6 +449,16 @@
         }
     }
 
+    @Override
+    public boolean isPulsePending() {
+        return mPulsePending;
+    }
+
+    @Override
+    public void setPulsePending(boolean isPulsePending) {
+        mPulsePending = isPulsePending;
+    }
+
     /**
      * Whether always-on-display is being suppressed. This does not affect wakeup gestures like
      * pickup and tap.
@@ -458,4 +466,22 @@
     public boolean isAlwaysOnSuppressed() {
         return mAlwaysOnSuppressed;
     }
+
+    final OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
+        @Override
+        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+            if (mStatusBarStateController.isDozing() && isHeadsUp) {
+                entry.setPulseSuppressed(false);
+                fireNotificationPulse(entry);
+                if (isPulsing()) {
+                    mDozeScrimController.cancelPendingPulseTimeout();
+                }
+            }
+            if (!isHeadsUp && !mHeadsUpManagerPhone.hasNotifications()) {
+                // There are no longer any notifications to show.  We should end the
+                // pulse now.
+                stopPulsing();
+            }
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 44aef7d..1dface2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -56,7 +56,9 @@
 
 /**
  * A class which manages the bouncer on the lockscreen.
+ * @deprecated Use KeyguardBouncerRepository
  */
+@Deprecated
 public class KeyguardBouncer {
 
     private static final String TAG = "KeyguardBouncer";
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 ce2c9c2..054bd28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -40,6 +40,7 @@
 import com.android.keyguard.CarrierTextController;
 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.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterViewController;
@@ -116,6 +117,7 @@
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
     private final Object mLock = new Object();
+    private final KeyguardLogger mLogger;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -279,7 +281,8 @@
             StatusBarUserInfoTracker statusBarUserInfoTracker,
             SecureSettings secureSettings,
             CommandQueue commandQueue,
-            @Main Executor mainExecutor
+            @Main Executor mainExecutor,
+            KeyguardLogger logger
     ) {
         super(view);
         mCarrierTextController = carrierTextController;
@@ -304,6 +307,7 @@
         mSecureSettings = secureSettings;
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
+        mLogger = logger;
 
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
         mKeyguardStateController.addCallback(
@@ -352,8 +356,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
         if (mTintedIconManager == null) {
-            mTintedIconManager =
-                    mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
+            mTintedIconManager = mTintedIconManagerFactory.create(
+                    mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
             mTintedIconManager.setBlockList(getBlockedIcons());
             mStatusBarIconController.addIconGroup(mTintedIconManager);
         }
@@ -430,6 +434,7 @@
 
     /** Animate the keyguard status bar in. */
     public void animateKeyguardStatusBarIn() {
+        mLogger.d("animating status bar in");
         if (mDisableStateTracker.isDisabled()) {
             // If our view is disabled, don't allow us to animate in.
             return;
@@ -445,6 +450,7 @@
 
     /** Animate the keyguard status bar out. */
     public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+        mLogger.d("animating status bar out");
         ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
         anim.addUpdateListener(mAnimatorUpdateListener);
         anim.setStartDelay(startDelay);
@@ -481,6 +487,9 @@
             newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                     * mKeyguardStatusBarAnimateAlpha
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
+            if (newAlpha != mView.getAlpha() && (newAlpha == 0 || newAlpha == 1)) {
+                mLogger.logStatusBarCalculatedAlpha(newAlpha);
+            }
         }
 
         boolean hideForBypass =
@@ -503,6 +512,10 @@
         if (mDisableStateTracker.isDisabled()) {
             visibility = View.INVISIBLE;
         }
+        if (visibility != mView.getVisibility()) {
+            mLogger.logStatusBarAlphaVisibility(visibility, alpha,
+                    StatusBarState.toString(mStatusBarState));
+        }
         mView.setAlpha(alpha);
         mView.setVisibility(visibility);
     }
@@ -596,6 +609,8 @@
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mExplicitAlpha: " + mExplicitAlpha);
+        pw.println("  alpha: " + mView.getAlpha());
+        pw.println("  visibility: " + mView.getVisibility());
         mView.dump(pw, args);
     }
 
@@ -605,6 +620,10 @@
      * @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
      */
     public void setAlpha(float alpha) {
+        if (mExplicitAlpha != alpha && (mExplicitAlpha == -1 || alpha == -1)) {
+            // logged if value changed to ignored or from ignored
+            mLogger.logStatusBarExplicitAlpha(alpha);
+        }
         mExplicitAlpha = alpha;
         updateViewState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 4d61689..00c3e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.FooterActionsView;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.user.UserSwitcherActivity;
 import com.android.systemui.util.ViewController;
@@ -49,7 +50,7 @@
     private final ActivityStarter mActivityStarter;
     private final FeatureFlags mFeatureFlags;
 
-    private UserSwitcherController.BaseUserAdapter mUserListener;
+    private BaseUserSwitcherAdapter mUserListener;
 
     private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
         @Override
@@ -135,7 +136,7 @@
 
             final UserSwitcherController controller = mUserSwitcherController;
             if (controller != null) {
-                mUserListener = new UserSwitcherController.BaseUserAdapter(controller) {
+                mUserListener = new BaseUserSwitcherAdapter(controller) {
                     @Override
                     public void notifyDataSetChanged() {
                         mView.refreshContentDescription(getCurrentUser());
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 7b8c5fc..5a70d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -278,6 +278,15 @@
         }
     }
 
+    @Override
+    public String toString() {
+        return "NotificationIconContainer("
+                + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+                + " inNotificationIconShelf=" + mInNotificationIconShelf
+                + " speedBumpIndex=" + mSpeedBumpIndex
+                + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+    }
+
     @VisibleForTesting
     public void setIconSize(int size) {
         mIconSize = size;
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 ae201e3..5512bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,8 +21,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -41,9 +39,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
-    private final NotificationsController mNotificationsController;
-    private final DozeServiceHost mDozeServiceHost;
-    private final DozeScrimController mDozeScrimController;
 
     @Inject
     StatusBarHeadsUpChangeListener(
@@ -53,10 +48,7 @@
             KeyguardBypassController keyguardBypassController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController statusBarStateController,
-            NotificationRemoteInputManager notificationRemoteInputManager,
-            NotificationsController notificationsController,
-            DozeServiceHost dozeServiceHost,
-            DozeScrimController dozeScrimController) {
+            NotificationRemoteInputManager notificationRemoteInputManager) {
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
@@ -65,9 +57,6 @@
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
-        mNotificationsController = notificationsController;
-        mDozeServiceHost = dozeServiceHost;
-        mDozeScrimController = dozeScrimController;
     }
 
     @Override
@@ -117,20 +106,4 @@
             }
         }
     }
-
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        if (mStatusBarStateController.isDozing() && isHeadsUp) {
-            entry.setPulseSuppressed(false);
-            mDozeServiceHost.fireNotificationPulse(entry);
-            if (mDozeServiceHost.isPulsing()) {
-                mDozeScrimController.cancelPendingPulseTimeout();
-            }
-        }
-        if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
-            // There are no longer any notifications to show.  We should end the
-            //pulse now.
-            mDozeScrimController.pulseOutNow();
-        }
-    }
 }
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 bd99713..ece7ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@
 
 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 android.annotation.Nullable;
@@ -38,7 +39,7 @@
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@
 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;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
 import com.android.systemui.util.Assert;
@@ -56,7 +61,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 public interface StatusBarIconController {
 
@@ -85,6 +89,12 @@
     void setMobileIcons(String slot, List<MobileIconState> states);
 
     /**
+     * This method completely replaces {@link #setMobileIcons} with the information from the new
+     * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+     * to worry about funneling MobileIconState objects through anymore.
+     */
+    void setNewMobileIconSubIds(List<Integer> subIds);
+    /**
      * Display the no calling & SMS icons.
      */
     void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -139,13 +149,17 @@
 
         public DarkIconManager(
                 LinearLayout linearLayout,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
+                    mobileUiAdapter,
                     mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
@@ -204,27 +218,32 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
+            private final MobileUiAdapter mMobileUiAdapter;
             private final DarkIconDispatcher mDarkIconDispatcher;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider,
+                    WifiViewModel wifiViewModel,
                     MobileContextProvider mobileContextProvider,
+                    MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModelProvider = wifiViewModelProvider;
+                mWifiViewModel = wifiViewModel;
                 mMobileContextProvider = mobileContextProvider;
+                mMobileUiAdapter = mobileUiAdapter;
                 mDarkIconDispatcher = darkIconDispatcher;
             }
 
-            public DarkIconManager create(LinearLayout group) {
+            public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
                 return new DarkIconManager(
                         group,
+                        location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModelProvider,
+                        mWifiViewModel,
+                        mMobileUiAdapter,
                         mMobileContextProvider,
                         mDarkIconDispatcher);
             }
@@ -239,12 +258,17 @@
 
         public TintedIconManager(
                 ViewGroup group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
-                MobileContextProvider mobileContextProvider) {
+                WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
+                MobileContextProvider mobileContextProvider
+        ) {
             super(group,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
+                    mobileUiAdapter,
                     mobileContextProvider);
         }
 
@@ -278,24 +302,30 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
+            private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider,
-                    MobileContextProvider mobileContextProvider) {
+                    WifiViewModel wifiViewModel,
+                    MobileUiAdapter mobileUiAdapter,
+                    MobileContextProvider mobileContextProvider
+            ) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModelProvider = wifiViewModelProvider;
+                mWifiViewModel = wifiViewModel;
+                mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
             }
 
-            public TintedIconManager create(ViewGroup group) {
+            public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
                 return new TintedIconManager(
                         group,
+                        location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModelProvider,
+                        mWifiViewModel,
+                        mMobileUiAdapter,
                         mMobileContextProvider);
             }
         }
@@ -306,9 +336,12 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
+        private final StatusBarLocation mLocation;
         private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-        private final Provider<WifiViewModel> mWifiViewModelProvider;
+        private final WifiViewModel mWifiViewModel;
         private final MobileContextProvider mMobileContextProvider;
+        private final MobileIconsViewModel mMobileIconsViewModel;
+
         protected final Context mContext;
         protected final int mIconSize;
         // Whether or not these icons show up in dumpsys
@@ -324,16 +357,28 @@
 
         public IconManager(
                 ViewGroup group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
-                MobileContextProvider mobileContextProvider) {
+                WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
+                MobileContextProvider mobileContextProvider
+        ) {
             mGroup = group;
+            mLocation = location;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
-            mWifiViewModelProvider = wifiViewModelProvider;
+            mWifiViewModel = wifiViewModel;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+
+            if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                // This starts the flow for the new pipeline, and will notify us of changes
+                mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+            } else {
+                mMobileIconsViewModel = null;
+            }
         }
 
         public boolean isDemoable() {
@@ -386,6 +431,9 @@
 
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
+
+                case TYPE_MOBILE_NEW:
+                    return addNewMobileIcon(index, slot, holder.getTag());
             }
 
             return null;
@@ -402,7 +450,7 @@
 
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            final BaseStatusBarWifiView view;
+            final BaseStatusBarFrameLayout view;
             if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
                 view = onCreateModernStatusBarWifiView(slot);
                 // When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -421,17 +469,47 @@
         }
 
         @VisibleForTesting
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+        protected StatusIconDisplayable addMobileIcon(
+                int index,
+                String slot,
+                MobileIconState state
+        ) {
+            if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                throw new IllegalStateException("Attempting to add a mobile icon while the new "
+                        + "pipeline is enabled is not supported");
+            }
+
             // Use the `subId` field as a key to query for the correct context
-            StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
-            view.applyMobileState(state);
-            mGroup.addView(view, index, onCreateLayoutParams());
+            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+            mobileView.applyMobileState(state);
+            mGroup.addView(mobileView, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
                 Context mobileContext = mMobileContextProvider
                         .getMobileContextForSub(state.subId, mContext);
                 mDemoStatusIcons.addMobileView(state, mobileContext);
             }
+            return mobileView;
+        }
+
+        protected StatusIconDisplayable addNewMobileIcon(
+                int index,
+                String slot,
+                int subId
+        ) {
+            if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                throw new IllegalStateException("Attempting to add a mobile icon using the new"
+                        + "pipeline, but the enabled flag is false.");
+            }
+
+            BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+            mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                // TODO (b/249790009): demo mode should be handled at the data layer in the
+                //  new pipeline
+            }
+
             return view;
         }
 
@@ -446,7 +524,7 @@
 
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
             return ModernStatusBarWifiView.constructAndBind(
-                    mContext, slot, mWifiViewModelProvider.get());
+                    mContext, slot, mWifiViewModel, mLocation);
         }
 
         private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -456,6 +534,15 @@
             return view;
         }
 
+        private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+                String slot, int subId) {
+            return ModernStatusBarMobileView
+                    .constructAndBind(
+                            mContext,
+                            slot,
+                            mMobileIconsViewModel.viewModelForSub(subId));
+        }
+
         protected LinearLayout.LayoutParams onCreateLayoutParams() {
             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
         }
@@ -511,6 +598,10 @@
                     return;
                 case TYPE_MOBILE:
                     onSetMobileIcon(viewIndex, holder.getMobileState());
+                    return;
+                case TYPE_MOBILE_NEW:
+                    // Nothing, the icon updates itself now
+                    return;
                 default:
                     break;
             }
@@ -534,9 +625,13 @@
         }
 
         public void onSetMobileIcon(int viewIndex, MobileIconState state) {
-            StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
-            if (view != null) {
-                view.applyMobileState(state);
+            View view = mGroup.getChildAt(viewIndex);
+            if (view instanceof StatusBarMobileView) {
+                ((StatusBarMobileView) view).applyMobileState(state);
+            } else {
+                // ModernStatusBarMobileView automatically updates via the ViewModel
+                throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+                        + "the new pipeline");
             }
 
             if (mIsInDemoMode) {
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 7c31366..e106b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@
 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;
 import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@
     private final StatusBarIconList mStatusBarIconList;
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconHideList = new ArraySet<>();
-
-    private Context mContext;
+    private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+    private final Context mContext;
 
     /** */
     @Inject
@@ -78,9 +79,12 @@
             ConfigurationController configurationController,
             TunerService tunerService,
             DumpManager dumpManager,
-            StatusBarIconList statusBarIconList) {
+            StatusBarIconList statusBarIconList,
+            StatusBarPipelineFlags statusBarPipelineFlags
+    ) {
         mStatusBarIconList = statusBarIconList;
         mContext = context;
+        mStatusBarPipelineFlags = statusBarPipelineFlags;
 
         configurationController.addCallback(this);
         commandQueue.addCallback(this);
@@ -220,6 +224,11 @@
      */
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+        if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+                    + "pipeline frontend is enabled");
+            return;
+        }
         Slot mobileSlot = mStatusBarIconList.getSlot(slot);
 
         // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@
         Collections.reverse(iconStates);
 
         for (MobileIconState state : iconStates) {
-
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@
         }
     }
 
+    @Override
+    public void setNewMobileIconSubIds(List<Integer> subIds) {
+        if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            Log.d(TAG, "ignoring new pipeline callback, "
+                    + "since the frontend is disabled");
+            return;
+        }
+        Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+        Collections.reverse(subIds);
+
+        for (Integer subId : subIds) {
+            StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+            if (holder == null) {
+                holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+                setIcon("mobile", holder);
+            } else {
+                // Don't have to do anything in the new world
+            }
+        }
+    }
+
     /**
      * Accept a list of CallIndicatorIconStates, and show the call strength icons.
      * @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@
         }
     }
 
-
-
     private void handleSet(String slotName, StatusBarIconHolder holder) {
         int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
         mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
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 af342dd..68a203e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@
 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;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@
     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
+     * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+     * to inform the old view system about changes to the data set (the list of mobile icons). The
+     * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+     * the need for this entire class.
+     *
+     * @deprecated This field only exists so the new status bar pipeline can interface with the
+     * view holder system.
+     */
+    @Deprecated
+    public static final int TYPE_MOBILE_NEW = 3;
+
+    @IntDef({
+            TYPE_ICON,
+            TYPE_WIFI,
+            TYPE_MOBILE,
+            TYPE_MOBILE_NEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface IconType {}
 
     private StatusBarIcon mIcon;
     private WifiIconState mWifiState;
     private MobileIconState mMobileState;
-    private int mType = TYPE_ICON;
+    private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
 
     private StatusBarIconHolder() {
-
     }
 
     public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@
     }
 
     /**
+     * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+     * determine icon ordering and building the correct view model
+     */
+    public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mType = TYPE_MOBILE_NEW;
+        holder.mTag = subId;
+
+        return holder;
+    }
+
+    /**
      * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
      */
     public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@
         return holder;
     }
 
-    public int getType() {
+    public @IconType int getType() {
         return mType;
     }
 
@@ -134,8 +171,12 @@
                 return mWifiState.visible;
             case TYPE_MOBILE:
                 return mMobileState.visible;
+            case TYPE_MOBILE_NEW:
+                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+                return true;
 
-            default: return true;
+            default:
+                return true;
         }
     }
 
@@ -156,6 +197,10 @@
             case TYPE_MOBILE:
                 mMobileState.visible = visible;
                 break;
+
+            case TYPE_MOBILE_NEW:
+                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+                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 6649f3a..04deecd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -46,6 +46,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
@@ -53,11 +54,20 @@
 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.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -67,9 +77,6 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
@@ -93,7 +100,7 @@
 @SysUISingleton
 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
         StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
-        PanelExpansionListener, NavigationModeController.ModeChangedListener,
+        ShadeExpansionListener, NavigationModeController.ModeChangedListener,
         KeyguardViewController, FoldAodAnimationController.FoldAodAnimationStatus {
 
     // When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
@@ -123,6 +130,9 @@
     @Nullable
     private final FoldAodAnimationController mFoldAodAnimationController;
     private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
+    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
+    private final BouncerInteractor mBouncerInteractor;
+    private final BouncerViewDelegate mBouncerViewDelegate;
     private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
 
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -194,10 +204,11 @@
     protected CentralSurfaces mCentralSurfaces;
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
+    private boolean mCentralSurfacesRegistered;
 
     private View mNotificationContainer;
 
-    protected KeyguardBouncer mBouncer;
+    @Nullable protected KeyguardBouncer mBouncer;
     protected boolean mShowing;
     protected boolean mOccluded;
     protected boolean mRemoteInputActive;
@@ -223,6 +234,7 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+    private boolean mIsModernBouncerEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -237,6 +249,7 @@
     private final DockManager mDockManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
     private final LatencyTracker mLatencyTracker;
+    private final KeyguardSecurityModel mKeyguardSecurityModel;
     private KeyguardBypassController mBypassController;
     @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
 
@@ -271,7 +284,12 @@
             KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
             Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
             Lazy<ShadeController> shadeController,
-            LatencyTracker latencyTracker) {
+            LatencyTracker latencyTracker,
+            KeyguardSecurityModel keyguardSecurityModel,
+            FeatureFlags featureFlags,
+            BouncerCallbackInteractor bouncerCallbackInteractor,
+            BouncerInteractor bouncerInteractor,
+            BouncerView bouncerView) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -288,14 +306,19 @@
         mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
+        mKeyguardSecurityModel = keyguardSecurityModel;
+        mBouncerCallbackInteractor = bouncerCallbackInteractor;
+        mBouncerInteractor = bouncerInteractor;
+        mBouncerViewDelegate = bouncerView.getDelegate();
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+        mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
     }
 
     @Override
     public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             NotificationPanelViewController notificationPanelViewController,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer,
             KeyguardBypassController bypassController) {
@@ -303,15 +326,20 @@
         mBiometricUnlockController = biometricUnlockController;
 
         ViewGroup container = mCentralSurfaces.getBouncerContainer();
-        mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+        if (mIsModernBouncerEnabled) {
+            mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
+        } else {
+            mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+        }
         mNotificationPanelViewController = notificationPanelViewController;
-        if (panelExpansionStateManager != null) {
-            panelExpansionStateManager.addExpansionListener(this);
+        if (shadeExpansionStateManager != null) {
+            shadeExpansionStateManager.addExpansionListener(this);
         }
         mBypassController = bypassController;
         mNotificationContainer = notificationContainer;
         mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
                 centralSurfaces.getKeyguardMessageArea());
+        mCentralSurfacesRegistered = true;
 
         registerListeners();
     }
@@ -358,7 +386,7 @@
     }
 
     @Override
-    public void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
+    public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
         float fraction = event.getFraction();
         boolean tracking = event.getTracking();
         // Avoid having the shade and the bouncer open at the same time over a dream.
@@ -377,29 +405,45 @@
         if (mDozing && !mPulsing) {
             return;
         } else if (mNotificationPanelViewController.isUnlockHintRunning()) {
-            mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            if (mBouncer != null) {
+                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            }
+            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
             return;
         } else if (bouncerNeedsScrimming()) {
-            mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+            if (mBouncer != null) {
+                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+            }
+            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
         } else if (mShowing && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
                     && !mCentralSurfaces.isInLaunchTransition()
                     && !isUnlockCollapsing()) {
-                mBouncer.setExpansion(fraction);
+                if (mBouncer != null) {
+                    mBouncer.setExpansion(fraction);
+                }
+                mBouncerInteractor.setExpansion(fraction);
             }
             if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
                     && !mKeyguardStateController.canDismissLockScreen()
-                    && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
-                mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+                    && !bouncerIsShowing()
+                    && !bouncerIsAnimatingAway()) {
+                if (mBouncer != null) {
+                    mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+                }
+                mBouncerInteractor.show(/* isScrimmed= */false);
             }
-        } else if (!mShowing && mBouncer.inTransit()) {
+        } else if (!mShowing && isBouncerInTransit()) {
             // Keyguard is not visible anymore, but expansion animation was still running.
             // We need to hide the bouncer, otherwise it will be stuck in transit.
-            mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            if (mBouncer != null) {
+                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            }
+            mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
             // unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -440,15 +484,20 @@
      * {@link KeyguardBouncer#needsFullscreenBouncer()}.
      */
     protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
-        if (mBouncer.needsFullscreenBouncer() && !mDozing) {
+        if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
             mCentralSurfaces.hideKeyguard();
-            mBouncer.show(true /* resetSecuritySelection */);
+            if (mBouncer != null) {
+                mBouncer.show(true /* resetSecuritySelection */);
+            }
+            mBouncerInteractor.show(true);
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
                 hideBouncer(false /* destroyView */);
-                mBouncer.prepare();
+                if (mBouncer != null) {
+                    mBouncer.prepare();
+                }
             }
         }
         updateStates();
@@ -480,10 +529,10 @@
      */
     @VisibleForTesting
     void hideBouncer(boolean destroyView) {
-        if (mBouncer == null) {
-            return;
+        if (mBouncer != null) {
+            mBouncer.hide(destroyView);
         }
-        mBouncer.hide(destroyView);
+        mBouncerInteractor.hide();
         if (mShowing) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
             // potential actions unless we actually unlocked.
@@ -501,8 +550,11 @@
     public void showBouncer(boolean scrimmed) {
         resetAlternateAuth(false);
 
-        if (mShowing && !mBouncer.isShowing()) {
-            mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+        if (mShowing && !isBouncerShowing()) {
+            if (mBouncer != null) {
+                mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+            }
+            mBouncerInteractor.show(scrimmed);
         }
         updateStates();
     }
@@ -535,7 +587,11 @@
                 // instead of the bouncer.
                 if (shouldShowAltAuth()) {
                     if (!afterKeyguardGone) {
-                        mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+                        if (mBouncer != null) {
+                            mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+                                    mKeyguardGoneCancelAction);
+                        }
+                        mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
                                 mKeyguardGoneCancelAction);
                         mAfterKeyguardGoneAction = null;
                         mKeyguardGoneCancelAction = null;
@@ -549,12 +605,18 @@
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    mBouncer.show(false /* resetSecuritySelection */);
+                    mBouncerInteractor.show(/* isScrimmed= */true);
+                    if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
-                    mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
-                            mKeyguardGoneCancelAction);
+                    mBouncerInteractor.setDismissAction(
+                            mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+                    mBouncerInteractor.show(/* isScrimmed= */true);
+                    if (mBouncer != null) {
+                        mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
+                                mKeyguardGoneCancelAction);
+                    }
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
                     mKeyguardGoneCancelAction = null;
@@ -591,7 +653,7 @@
             // Hide bouncer and quick-quick settings.
             if (mOccluded && !mDozing) {
                 mCentralSurfaces.hideKeyguard();
-                if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
+                if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
                     hideBouncer(false /* destroyView */);
                 }
             } else {
@@ -655,7 +717,10 @@
 
     @Override
     public void onFinishedGoingToSleep() {
-        mBouncer.onScreenTurnedOff();
+        if (mBouncer != null) {
+            mBouncer.onScreenTurnedOff();
+        }
+        mBouncerInteractor.onScreenTurnedOff();
     }
 
     @Override
@@ -667,7 +732,14 @@
     private void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            reset(true /* hideBouncerWhenShowing */);
+            if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+                reset(dozing /* hideBouncerWhenShowing */);
+            }
+
+            if (bouncerIsOrWillBeShowing()) {
+                // Ensure bouncer is not shown when dozing state changes.
+                hideBouncer(false);
+            }
             updateStates();
 
             if (!dozing) {
@@ -746,7 +818,7 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (animate && !mOccluded && mShowing && !mBouncer.isShowing()) {
+        if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
             mCentralSurfaces.animateKeyguardUnoccluding();
         }
     }
@@ -762,8 +834,11 @@
 
     @Override
     public void startPreHideAnimation(Runnable finishRunnable) {
-        if (mBouncer.isShowing()) {
-            mBouncer.startPreHideAnimation(finishRunnable);
+        if (bouncerIsShowing()) {
+            if (mBouncer != null) {
+                mBouncer.startPreHideAnimation(finishRunnable);
+            }
+            mBouncerInteractor.startDisappearAnimation(finishRunnable);
             mCentralSurfaces.onBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
@@ -873,8 +948,12 @@
     }
 
     public void onThemeChanged() {
-        boolean wasShowing = mBouncer.isShowing();
-        boolean wasScrimmed = mBouncer.isScrimmed();
+        if (mIsModernBouncerEnabled) {
+            updateResources();
+            return;
+        }
+        boolean wasShowing = bouncerIsShowing();
+        boolean wasScrimmed = bouncerIsScrimmed();
 
         hideBouncer(true /* destroyView */);
         mBouncer.prepare();
@@ -923,7 +1002,12 @@
      * WARNING: This method might cause Binder calls.
      */
     public boolean isSecure() {
-        return mBouncer.isSecure();
+        if (mBouncer != null) {
+            return mBouncer.isSecure();
+        }
+
+        return mKeyguardSecurityModel.getSecurityMode(
+                KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
     }
 
     @Override
@@ -940,10 +1024,11 @@
      * @return whether the back press has been handled
      */
     public boolean onBackPressed(boolean hideImmediately) {
-        if (mBouncer.isShowing()) {
+        if (bouncerIsShowing()) {
             mCentralSurfaces.endAffordanceLaunch();
             // The second condition is for SIM card locked bouncer
-            if (mBouncer.isScrimmed() && !mBouncer.needsFullscreenBouncer()) {
+            if (bouncerIsScrimmed()
+                    && !needsFullscreenBouncer()) {
                 hideBouncer(false);
                 updateStates();
             } else {
@@ -956,16 +1041,19 @@
 
     @Override
     public boolean isBouncerShowing() {
-        return mBouncer.isShowing() || isShowingAlternateAuth();
+        return bouncerIsShowing() || isShowingAlternateAuth();
     }
 
     @Override
     public boolean bouncerIsOrWillBeShowing() {
-        return isBouncerShowing() || mBouncer.inTransit();
+        return isBouncerShowing() || isBouncerInTransit();
     }
 
     public boolean isFullscreenBouncer() {
-        return mBouncer.isFullscreenBouncer();
+        if (mBouncerViewDelegate != null) {
+            return mBouncerViewDelegate.isFullScreenBouncer();
+        }
+        return mBouncer != null && mBouncer.isFullscreenBouncer();
     }
 
     /**
@@ -986,7 +1074,7 @@
     private long getNavBarShowDelay() {
         if (mKeyguardStateController.isKeyguardFadingAway()) {
             return mKeyguardStateController.getKeyguardFadingAwayDelay();
-        } else if (mBouncer.isShowing()) {
+        } else if (isBouncerShowing()) {
             return NAV_BAR_SHOW_DELAY_BOUNCER;
         } else {
             // No longer dozing, or remote input is active. No delay.
@@ -1007,20 +1095,29 @@
     };
 
     protected void updateStates() {
+        if (!mCentralSurfacesRegistered) {
+            return;
+        }
         boolean showing = mShowing;
         boolean occluded = mOccluded;
-        boolean bouncerShowing = mBouncer.isShowing();
+        boolean bouncerShowing = bouncerIsShowing();
         boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
-        boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
+        boolean bouncerDismissible = !isFullscreenBouncer();
         boolean remoteInputActive = mRemoteInputActive;
 
         if ((bouncerDismissible || !showing || remoteInputActive) !=
                 (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
             if (bouncerDismissible || !showing || remoteInputActive) {
-                mBouncer.setBackButtonEnabled(true);
+                if (mBouncer != null) {
+                    mBouncer.setBackButtonEnabled(true);
+                }
+                mBouncerInteractor.setBackButtonEnabled(true);
             } else {
-                mBouncer.setBackButtonEnabled(false);
+                if (mBouncer != null) {
+                    mBouncer.setBackButtonEnabled(false);
+                }
+                mBouncerInteractor.setBackButtonEnabled(false);
             }
         }
 
@@ -1097,7 +1194,9 @@
                 || mPulsing && !mIsDocked)
                 && mGesturalNav;
         return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
-                || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav
+                || bouncerIsShowing()
+                || mRemoteInputActive
+                || keyguardWithGestureNav
                 || mGlobalActionsVisible);
     }
 
@@ -1116,18 +1215,27 @@
     }
 
     public boolean shouldDismissOnMenuPressed() {
-        return mBouncer.shouldDismissOnMenuPressed();
+        if (mBouncerViewDelegate != null) {
+            return mBouncerViewDelegate.shouldDismissOnMenuPressed();
+        }
+        return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
-        return mBouncer.interceptMediaKey(event);
+        if (mBouncerViewDelegate != null) {
+            return mBouncerViewDelegate.interceptMediaKey(event);
+        }
+        return mBouncer != null && mBouncer.interceptMediaKey(event);
     }
 
     /**
      * @return true if the pre IME back event should be handled
      */
     public boolean dispatchBackKeyEventPreIme() {
-        return mBouncer.dispatchBackKeyEventPreIme();
+        if (mBouncerViewDelegate != null) {
+            return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
+        }
+        return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
     }
 
     public void readyForKeyguardDone() {
@@ -1150,7 +1258,7 @@
     }
 
     public boolean isSecure(int userId) {
-        return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
+        return isSecure() || mLockPatternUtils.isSecure(userId);
     }
 
     @Override
@@ -1173,7 +1281,10 @@
      * fingerprint.
      */
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        mBouncer.notifyKeyguardAuthenticated(strongAuth);
+        if (mBouncer != null) {
+            mBouncer.notifyKeyguardAuthenticated(strongAuth);
+        }
+        mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
 
         if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
             resetAlternateAuth(false);
@@ -1188,7 +1299,10 @@
                 mKeyguardMessageAreaController.setMessage(message);
             }
         } else {
-            mBouncer.showMessage(message, colorState);
+            if (mBouncer != null) {
+                mBouncer.showMessage(message, colorState);
+            }
+            mBouncerInteractor.showMessage(message, colorState);
         }
     }
 
@@ -1221,9 +1335,10 @@
     public boolean bouncerNeedsScrimming() {
         // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
         return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
-                || mBouncer.willDismissWithAction()
-                || (mBouncer.isShowing() && mBouncer.isScrimmed())
-                || mBouncer.isFullscreenBouncer();
+                || bouncerWillDismissWithAction()
+                || (bouncerIsShowing()
+                && bouncerIsScrimmed())
+                || isFullscreenBouncer();
     }
 
     /**
@@ -1235,6 +1350,7 @@
         if (mBouncer != null) {
             mBouncer.updateResources();
         }
+        mBouncerInteractor.updateResources();
     }
 
     public void dump(PrintWriter pw) {
@@ -1288,6 +1404,7 @@
         }
     }
 
+    @Nullable
     public KeyguardBouncer getBouncer() {
         return mBouncer;
     }
@@ -1319,6 +1436,8 @@
         if (mBouncer != null) {
             mBouncer.updateKeyguardPosition(x);
         }
+
+        mBouncerInteractor.setKeyguardPosition(x);
     }
 
     private static class DismissWithActionRequest {
@@ -1358,9 +1477,65 @@
      * Returns if bouncer expansion is between 0 and 1 non-inclusive.
      */
     public boolean isBouncerInTransit() {
-        if (mBouncer == null) return false;
+        if (mBouncer != null) {
+            return mBouncer.inTransit();
+        }
 
-        return mBouncer.inTransit();
+        return mBouncerInteractor.isInTransit();
+    }
+
+    /**
+     * Returns if bouncer is showing
+     */
+    public boolean bouncerIsShowing() {
+        if (mBouncer != null) {
+            return mBouncer.isShowing();
+        }
+
+        return mBouncerInteractor.isFullyShowing();
+    }
+
+    /**
+     * Returns if bouncer is scrimmed
+     */
+    public boolean bouncerIsScrimmed() {
+        if (mBouncer != null) {
+            return mBouncer.isScrimmed();
+        }
+
+        return mBouncerInteractor.isScrimmed();
+    }
+
+    /**
+     * Returns if bouncer is animating away
+     */
+    public boolean bouncerIsAnimatingAway() {
+        if (mBouncer != null) {
+            return mBouncer.isAnimatingAway();
+        }
+
+        return mBouncerInteractor.isAnimatingAway();
+    }
+
+    /**
+     * Returns if bouncer will dismiss with action
+     */
+    public boolean bouncerWillDismissWithAction() {
+        if (mBouncer != null) {
+            return mBouncer.willDismissWithAction();
+        }
+
+        return mBouncerInteractor.willDismissWithAction();
+    }
+
+    /**
+     * Returns if bouncer needs fullscreen bouncer. i.e. sim pin security method
+     */
+    public boolean needsFullscreenBouncer() {
+        KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
+                KeyguardUpdateMonitor.getCurrentUser());
+        return mode == KeyguardSecurityModel.SecurityMode.SimPin
+                || mode == KeyguardSecurityModel.SecurityMode.SimPuk;
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
similarity index 66%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
index 9b370d8..5ace226 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility.cursor;
+package com.android.systemui.statusbar.phone
 
-/**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
- */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
+/** An enumeration of the different locations that host a status bar. */
+enum class StatusBarLocation {
+    /** Home screen or in-app. */
+    HOME,
+    /** Keyguard (aka lockscreen). */
+    KEYGUARD,
+    /** Quick settings (inside the shade). */
+    QS,
 }
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 fb5b096..0369845 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
@@ -41,6 +41,7 @@
 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.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
@@ -63,7 +64,6 @@
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -283,7 +283,7 @@
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
             NotificationIconAreaController notificationIconAreaController,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             FeatureFlags featureFlags,
             StatusBarIconController statusBarIconController,
             StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
@@ -304,7 +304,7 @@
                 animationScheduler,
                 locationPublisher,
                 notificationIconAreaController,
-                panelExpansionStateManager,
+                shadeExpansionStateManager,
                 featureFlags,
                 statusBarIconController,
                 darkIconManagerFactory,
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 891f657..f09c79b 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
@@ -52,6 +52,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
@@ -64,12 +65,12 @@
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
@@ -120,7 +121,7 @@
     private final StatusBarLocationPublisher mLocationPublisher;
     private final FeatureFlags mFeatureFlags;
     private final NotificationIconAreaController mNotificationIconAreaController;
-    private final PanelExpansionStateManager mPanelExpansionStateManager;
+    private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarIconController mStatusBarIconController;
     private final CarrierConfigTracker mCarrierConfigTracker;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@@ -170,7 +171,7 @@
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
             NotificationIconAreaController notificationIconAreaController,
-            PanelExpansionStateManager panelExpansionStateManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             FeatureFlags featureFlags,
             StatusBarIconController statusBarIconController,
             StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
@@ -191,7 +192,7 @@
         mAnimationScheduler = animationScheduler;
         mLocationPublisher = locationPublisher;
         mNotificationIconAreaController = notificationIconAreaController;
-        mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeExpansionStateManager = shadeExpansionStateManager;
         mFeatureFlags = featureFlags;
         mStatusBarIconController = statusBarIconController;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
@@ -235,7 +236,8 @@
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
         }
-        mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
+        mDarkIconManager = mDarkIconManagerFactory.create(
+                view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
         mDarkIconManager.setShouldLog(true);
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
@@ -460,7 +462,7 @@
     }
 
     private boolean shouldHideNotificationIcons() {
-        if (!mPanelExpansionStateManager.isClosed()
+        if (!mShadeExpansionStateManager.isClosed()
                 && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
             return true;
         }
@@ -506,7 +508,7 @@
      * don't set the clock GONE otherwise it'll mess up the animation.
      */
     private int clockHiddenMode() {
-        if (!mPanelExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
+        if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
                 && !mStatusBarStateController.isDozing()) {
             return View.INVISIBLE;
         }
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 9a7c3fa..06d5542 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
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@
 
     @Binds
     abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+    @Binds
+    abstract fun mobileSubscriptionRepository(
+        impl: MobileSubscriptionRepositoryImpl
+    ): MobileSubscriptionRepository
+
+    @Binds
+    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 0000000..46ccf32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.data.model
+
+import android.annotation.IntRange
+import android.telephony.Annotation.DataActivityType
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Data class containing all of the relevant information for a particular line of service, known as
+ * a Subscription in the telephony world. These models are the result of a single telephony listener
+ * which has many callbacks which each modify some particular field on this object.
+ *
+ * The design goal here is to de-normalize fields from the system into our model fields below. So
+ * any new field that needs to be tracked should be copied into this data class rather than
+ * threading complex system objects through the pipeline.
+ */
+data class MobileSubscriptionModel(
+    /** From [ServiceStateListener.onServiceStateChanged] */
+    val isEmergencyOnly: Boolean = false,
+
+    /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+    val isGsm: Boolean = false,
+    @IntRange(from = 0, to = 4)
+    val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+    @IntRange(from = 0, to = 4)
+    val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+
+    /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+    val dataConnectionState: Int? = null,
+
+    /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+    @DataActivityType val dataActivityDirection: Int? = null,
+
+    /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+    val carrierNetworkChangeActive: Boolean? = null,
+
+    /** From [DisplayInfoListener.onDisplayInfoChanged] */
+    val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 0000000..36de2a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+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.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+    /** Observable list of current mobile subscriptions */
+    val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+    /** Observable for the subscriptionId of the current mobile data connection */
+    val activeMobileDataSubscriptionId: Flow<Int>
+
+    /** Get or create an observable for the given subscription ID */
+    fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+    private val subscriptionManager: SubscriptionManager,
+    private val telephonyManager: TelephonyManager,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+    private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+    /**
+     * State flow that emits the set of mobile data subscriptions, each represented by its own
+     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+     * info object, but for now we keep track of the infos themselves.
+     */
+    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                        override fun onSubscriptionsChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                    bgDispatcher.asExecutor(),
+                    callback,
+                )
+
+                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+            }
+            .mapLatest { fetchSubscriptionsList() }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+    /** StateFlow that keeps track of the current active mobile data subscription */
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+                            trySend(subId)
+                        }
+                    }
+
+                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+            }
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID
+            )
+
+    /**
+     * Each mobile subscription needs its own flow, which comes from registering listeners on the
+     * system. Use this method to create those flows and cache them for reuse
+     */
+    override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+        return subIdFlowCache[subId]
+            ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+    }
+
+    @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+    private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+        var state = MobileSubscriptionModel()
+        conflatedCallbackFlow {
+                val phony = telephonyManager.createForSubscriptionId(subId)
+                // TODO (b/240569788): log all of these into the connectivity logger
+                val callback =
+                    object :
+                        TelephonyCallback(),
+                        ServiceStateListener,
+                        SignalStrengthsListener,
+                        DataConnectionStateListener,
+                        DataActivityListener,
+                        CarrierNetworkListener,
+                        DisplayInfoListener {
+                        override fun onServiceStateChanged(serviceState: ServiceState) {
+                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            trySend(state)
+                        }
+                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            val cdmaLevel =
+                                signalStrength
+                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+                                    .let { strengths ->
+                                        if (!strengths.isEmpty()) {
+                                            strengths[0].level
+                                        } else {
+                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                                        }
+                                    }
+
+                            val primaryLevel = signalStrength.level
+
+                            state =
+                                state.copy(
+                                    cdmaLevel = cdmaLevel,
+                                    primaryLevel = primaryLevel,
+                                    isGsm = signalStrength.isGsm,
+                                )
+                            trySend(state)
+                        }
+                        override fun onDataConnectionStateChanged(
+                            dataState: Int,
+                            networkType: Int
+                        ) {
+                            state = state.copy(dataConnectionState = dataState)
+                            trySend(state)
+                        }
+                        override fun onDataActivity(direction: Int) {
+                            state = state.copy(dataActivityDirection = direction)
+                            trySend(state)
+                        }
+                        override fun onCarrierNetworkChange(active: Boolean) {
+                            state = state.copy(carrierNetworkChangeActive = active)
+                            trySend(state)
+                        }
+                        override fun onDisplayInfoChanged(
+                            telephonyDisplayInfo: TelephonyDisplayInfo
+                        ) {
+                            state = state.copy(displayInfo = telephonyDisplayInfo)
+                            trySend(state)
+                        }
+                    }
+                phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose {
+                    phony.unregisterTelephonyCallback(callback)
+                    // Release the cached flow
+                    subIdFlowCache.remove(subId)
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+    }
+
+    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..77de849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.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.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+    /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+    val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+    private val deviceProvisionedController: DeviceProvisionedController,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application scope: CoroutineScope,
+) : UserSetupRepository {
+    /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+    override val isUserSetupFlow: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DeviceProvisionedController.DeviceProvisionedListener {
+                        override fun onUserSetupChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                deviceProvisionedController.addCallback(callback)
+
+                awaitClose { deviceProvisionedController.removeCallback(callback) }
+            }
+            .onStart { emit(Unit) }
+            .mapLatest { fetchUserSetupState() }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    private suspend fun fetchUserSetupState(): Boolean =
+        withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
new file mode 100644
index 0000000..40fe0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+    /** Identifier for RAT type indicator */
+    val iconGroup: Flow<SignalIcon.MobileIconGroup>
+    /** True if this line of service is emergency-only */
+    val isEmergencyOnly: Flow<Boolean>
+    /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+    val level: Flow<Int>
+    /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+    val numberOfLevels: Flow<Int>
+    /** True when we want to draw an icon that makes room for the exclamation mark */
+    val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+    mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+    override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+    override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+    override val level: Flow<Int> =
+        mobileStatusInfo.map { mobileModel ->
+            // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+            if (mobileModel.isGsm) {
+                mobileModel.primaryLevel
+            } else {
+                mobileModel.cdmaLevel
+            }
+        }
+
+    /**
+     * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+     * once it's wired up inside of [CarrierConfigTracker]
+     */
+    override val numberOfLevels: Flow<Int> = flowOf(4)
+
+    /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+    // TODO: find a better name for this?
+    override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 0000000..8e67e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+    private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+    private val carrierConfigTracker: CarrierConfigTracker,
+    userSetupRepo: UserSetupRepository,
+) {
+    private val activeMobileDataSubscriptionId =
+        mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+    private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+        mobileSubscriptionRepo.subscriptionsFlow
+
+    /**
+     * Generally, SystemUI wants to show iconography for each subscription that is listed by
+     * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+     * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+     *
+     * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+     * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+     *
+     * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+     * subscriptions based on
+     * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+     * and by checking which subscription is opportunistic, or which one is active.
+     */
+    val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+        combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+            ->
+            // Based on the old logic,
+            if (unfilteredSubs.size != 2) {
+                return@combine unfilteredSubs
+            }
+
+            val info1 = unfilteredSubs[0]
+            val info2 = unfilteredSubs[1]
+            // If both subscriptions are primary, show both
+            if (!info1.isOpportunistic && !info2.isOpportunistic) {
+                return@combine unfilteredSubs
+            }
+
+            // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+            // If carrier required, always show the icon of the primary subscription.
+            // Otherwise, show whichever subscription is currently active for internet.
+            if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+                // return the non-opportunistic info
+                return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+            } else {
+                return@combine if (info1.subscriptionId == activeId) {
+                    listOf(info1)
+                } else {
+                    listOf(info2)
+                }
+            }
+        }
+
+    val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+    /** Vends out new [MobileIconInteractor] for a particular subId */
+    fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+        MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+    /**
+     * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+     */
+    private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+        mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 0000000..380017c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+    interactor: MobileIconsInteractor,
+    private val iconController: StatusBarIconController,
+    private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    @Application scope: CoroutineScope,
+) {
+    private val mobileSubIds: Flow<List<Int>> =
+        interactor.filteredSubscriptions.mapLatest { infos ->
+            infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+        }
+
+    /**
+     * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+     * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+     * house the mobile infos.
+     *
+     * NOTE: this should go away as the view presenter learns more about this data pipeline
+     */
+    private val mobileSubIdsState: StateFlow<List<Int>> =
+        mobileSubIds
+            .onEach {
+                // Notify the icon controller here so that it knows to add icons
+                iconController.setNewMobileIconSubIds(it)
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    /**
+     * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+     * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+     * the old view system.
+     */
+    fun createMobileIconsViewModel(): MobileIconsViewModel =
+        iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 0000000..1405b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+    /** Binds the view to the view-model, continuing to update the former based on the latter */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: MobileIconViewModel,
+    ) {
+        val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+        val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+        view.isVisible = true
+        iconView.isVisible = true
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                // Set the icon for the triangle
+                launch {
+                    viewModel.iconId.distinctUntilChanged().collect { iconId ->
+                        mobileDrawable.level = iconId
+                    }
+                }
+
+                // Set the tint
+                launch {
+                    viewModel.tint.collect { tint ->
+                        iconView.imageTintList = ColorStateList.valueOf(tint)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 0000000..e7d5ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+    /**
+     * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+     * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+     * collecting, [MobileUiAdapter] will send updates to the icon manager.
+     */
+    @JvmStatic
+    fun bind(view: View, viewModel: MobileIconsViewModel) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.subscriptionIdsFlow.collect {
+                        // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+                        //  sets up a side-effect in this flow to trigger the methods on
+                        // [StatusBarIconController] which allows for this pipeline to be a data
+                        // source for the mobile icons.
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 0000000..ec4fa9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+    context: Context,
+    attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+    private lateinit var slot: String
+    override fun getSlot() = slot
+
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        // TODO
+    }
+
+    override fun setStaticDrawableColor(color: Int) {
+        // TODO
+    }
+
+    override fun setDecorColor(color: Int) {
+        // TODO
+    }
+
+    override fun setVisibleState(state: Int, animate: Boolean) {
+        // TODO
+    }
+
+    override fun getVisibleState(): Int {
+        return STATE_ICON
+    }
+
+    override fun isIconVisible(): Boolean {
+        return true
+    }
+
+    companion object {
+
+        /**
+         * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+         * returns it.
+         */
+        @JvmStatic
+        fun constructAndBind(
+            context: Context,
+            slot: String,
+            viewModel: MobileIconViewModel,
+        ): ModernStatusBarMobileView {
+            return (LayoutInflater.from(context)
+                    .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+                    as ModernStatusBarMobileView)
+                .also {
+                    it.slot = slot
+                    MobileIconBinder.bind(it, viewModel)
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 0000000..cfabeba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+    val subscriptionId: Int,
+    iconInteractor: MobileIconInteractor,
+    logger: ConnectivityPipelineLogger,
+) {
+    /** An int consumable by [SignalDrawable] for display */
+    var iconId: Flow<Int> =
+        combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+                level,
+                numberOfLevels,
+                cutOut ->
+                SignalDrawable.getState(level, numberOfLevels, cutOut)
+            }
+            .distinctUntilChanged()
+            .logOutputChange(logger, "iconId($subscriptionId)")
+
+    var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 0000000..24c1db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+    val subscriptionIdsFlow: Flow<List<Int>>,
+    private val interactor: MobileIconsInteractor,
+    private val logger: ConnectivityPipelineLogger,
+) {
+    /** TODO: do we need to cache these? */
+    fun viewModelForSub(subId: Int): MobileIconViewModel =
+        MobileIconViewModel(
+            subId,
+            interactor.createMobileConnectionInteractorForSubId(subId),
+            logger
+        )
+
+    class Factory
+    @Inject
+    constructor(
+        private val interactor: MobileIconsInteractor,
+        private val logger: ConnectivityPipelineLogger,
+    ) {
+        fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+            return MobileIconsViewModel(
+                subscriptionIdsFlow,
+                interactor,
+                logger,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
new file mode 100644
index 0000000..118b94c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.shared
+
+import android.telephony.TelephonyManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * An object storing constants that are used for calculating connectivity icons.
+ *
+ * Stored in a class for logging purposes.
+ */
+@SysUISingleton
+class ConnectivityConstants
+@Inject
+constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
+    init {
+        dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityConstants", this)
+    }
+
+    /** True if this device has the capability for data connections and false otherwise. */
+    val hasDataCapabilities = telephonyManager.isDataCapable
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.apply { println("hasDataCapabilities=$hasDataCapabilities") }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 88d8a86..dbb1aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -33,6 +33,20 @@
 ) {
     /**
      * Logs a change in one of the **raw inputs** to the connectivity pipeline.
+     *
+     * Use this method for inputs that don't have any extra information besides their callback name.
+     */
+    fun logInputChange(callbackName: String) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            { str1 = callbackName },
+            { "Input: $str1" }
+        )
+    }
+
+    /**
+     * Logs a change in one of the **raw inputs** to the connectivity pipeline.
      */
     fun logInputChange(callbackName: String, changeInfo: String?) {
         buffer.log(
@@ -128,12 +142,36 @@
         const val SB_LOGGING_TAG = "SbConnectivity"
 
         /**
+         * Log a change in one of the **inputs** to the connectivity pipeline.
+         */
+        fun Flow<Unit>.logInputChange(
+            logger: ConnectivityPipelineLogger,
+            inputParamName: String,
+        ): Flow<Unit> {
+            return this.onEach { logger.logInputChange(inputParamName) }
+        }
+
+        /**
+         * Log a change in one of the **inputs** to the connectivity pipeline.
+         *
+         * @param prettyPrint an optional function to transform the value into a readable string.
+         *   [toString] is used if no custom function is provided.
+         */
+        fun <T> Flow<T>.logInputChange(
+            logger: ConnectivityPipelineLogger,
+            inputParamName: String,
+            prettyPrint: (T) -> String = { it.toString() }
+        ): Flow<T> {
+            return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+        }
+
+        /**
          * Log a change in one of the **outputs** to the connectivity pipeline.
          *
          * @param prettyPrint an optional function to transform the value into a readable string.
          *   [toString] is used if no custom function is provided.
          */
-        fun <T : Any> Flow<T>.logOutputChange(
+        fun <T> Flow<T>.logOutputChange(
                 logger: ConnectivityPipelineLogger,
                 outputParamName: String,
                 prettyPrint: (T) -> String = { it.toString() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 103f3fc..681cf72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
 import android.annotation.SuppressLint
+import android.content.IntentFilter
 import android.net.ConnectivityManager
 import android.net.Network
 import android.net.NetworkCapabilities
@@ -30,51 +31,87 @@
 import android.net.wifi.WifiManager.TrafficStateCallback
 import android.util.Log
 import com.android.settingslib.Utils
+import com.android.systemui.broadcast.BroadcastDispatcher
 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.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.stateIn
 
-/**
- * Provides data related to the wifi state.
- */
+/** Provides data related to the wifi state. */
 interface WifiRepository {
-    /**
-     * Observable for the current wifi network.
-     */
-    val wifiNetwork: Flow<WifiNetworkModel>
+    /** Observable for the current wifi enabled status. */
+    val isWifiEnabled: StateFlow<Boolean>
 
-    /**
-     * Observable for the current wifi network activity.
-     */
-    val wifiActivity: Flow<WifiActivityModel>
+    /** Observable for the current wifi network. */
+    val wifiNetwork: StateFlow<WifiNetworkModel>
+
+    /** Observable for the current wifi network activity. */
+    val wifiActivity: StateFlow<WifiActivityModel>
 }
 
 /** Real implementation of [WifiRepository]. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 @SuppressLint("MissingPermission")
 class WifiRepositoryImpl @Inject constructor(
+    broadcastDispatcher: BroadcastDispatcher,
     connectivityManager: ConnectivityManager,
     logger: ConnectivityPipelineLogger,
     @Main mainExecutor: Executor,
     @Application scope: CoroutineScope,
     wifiManager: WifiManager?,
 ) : WifiRepository {
-    override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
+
+    private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
+        IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
+    )
+        .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
+
+    private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+
+    override val isWifiEnabled: StateFlow<Boolean> =
+        if (wifiManager == null) {
+            MutableStateFlow(false).asStateFlow()
+        } else {
+            // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
+            // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
+            // have changed.
+            merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
+                .mapLatest { wifiManager.isWifiEnabled }
+                .distinctUntilChanged()
+                .logOutputChange(logger, "enabled")
+                .stateIn(
+                    scope = scope,
+                    started = SharingStarted.WhileSubscribed(),
+                    initialValue = wifiManager.isWifiEnabled
+                )
+        }
+
+    override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
         var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
 
         val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
@@ -84,6 +121,8 @@
             ) {
                 logger.logOnCapabilitiesChanged(network, networkCapabilities)
 
+                wifiNetworkChangeEvents.tryEmit(Unit)
+
                 val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
                 if (wifiInfo?.isPrimary == true) {
                     val wifiNetworkModel = createWifiNetworkModel(
@@ -104,6 +143,9 @@
 
             override fun onLost(network: Network) {
                 logger.logOnLost(network)
+
+                wifiNetworkChangeEvents.tryEmit(Unit)
+
                 val wifi = currentWifi
                 if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
                     val newNetworkModel = WifiNetworkModel.Inactive
@@ -132,7 +174,7 @@
             initialValue = WIFI_NETWORK_DEFAULT
         )
 
-    override val wifiActivity: Flow<WifiActivityModel> =
+    override val wifiActivity: StateFlow<WifiActivityModel> =
             if (wifiManager == null) {
                 Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
                 flowOf(ACTIVITY_DEFAULT)
@@ -142,13 +184,15 @@
                         logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
                         trySend(trafficStateToWifiActivityModel(state))
                     }
-
-                    trySend(ACTIVITY_DEFAULT)
                     wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-
                     awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
                 }
             }
+                .stateIn(
+                    scope,
+                    started = SharingStarted.WhileSubscribed(),
+                    initialValue = ACTIVITY_DEFAULT
+                )
 
     companion object {
         val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 952525d..04b17ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -22,9 +22,10 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 
 /**
@@ -38,7 +39,11 @@
     connectivityRepository: ConnectivityRepository,
     wifiRepository: WifiRepository,
 ) {
-    private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+    /**
+     * The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
+     * have a network but no valid SSID.
+     */
+    val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
         when (info) {
             is WifiNetworkModel.Inactive -> null
             is WifiNetworkModel.CarrierMerged -> null
@@ -51,17 +56,17 @@
         }
     }
 
+    /** Our current enabled status. */
+    val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+
     /** Our current wifi network. See [WifiNetworkModel]. */
     val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
 
+    /** Our current wifi activity. See [WifiActivityModel]. */
+    val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+
     /** True if we're configured to force-hide the wifi icon and false otherwise. */
     val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
         it.contains(ConnectivitySlot.WIFI)
     }
-
-    /** True if our wifi network has activity in (download), and false otherwise. */
-    val hasActivityIn: Flow<Boolean> =
-        combine(wifiRepository.wifiActivity, ssid) { activity, ssid ->
-            activity.hasActivityIn && ssid != null
-        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index a19d1bd..0eb4b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -41,9 +41,14 @@
     /** True if we should show the activityIn/activityOut icons and false otherwise. */
     val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
 
+    /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
+    val alwaysShowIconIfEnabled =
+        context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.apply {
             println("shouldShowActivityConfig=$shouldShowActivityConfig")
+            println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
index 44c0496..5746106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
-/**
- * Provides information on the current wifi activity.
- */
+/** Provides information on the current wifi activity. */
 data class WifiActivityModel(
     /** True if the wifi has activity in (download). */
     val hasActivityIn: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 4fad327..273be63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -26,8 +26,15 @@
 import com.android.systemui.R
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
@@ -41,40 +48,111 @@
  */
 @OptIn(InternalCoroutinesApi::class)
 object WifiViewBinder {
-    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
+     */
+    interface Binding {
+        /** Returns true if the wifi icon should be visible and false otherwise. */
+        fun getShouldIconBeVisible(): Boolean
+
+        /** Notifies that the visibility state has changed. */
+        fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+    }
+
+    /**
+     * Binds the view to the appropriate view-model based on the given location. The view will
+     * continue to be updated following updates from the view-model.
+     */
     @JvmStatic
     fun bind(
         view: ViewGroup,
-        viewModel: WifiViewModel,
-    ) {
+        wifiViewModel: WifiViewModel,
+        location: StatusBarLocation,
+    ): Binding {
+        return when (location) {
+            StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
+            StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
+            StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
+        }
+    }
+
+    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @JvmStatic
+    private fun bind(
+        view: ViewGroup,
+        viewModel: LocationBasedWifiViewModel,
+    ): Binding {
+        val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
         val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
+        val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
+        val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
+        val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
+        val activityContainerView = view.requireViewById<View>(R.id.inout_container)
 
         view.isVisible = true
         iconView.isVisible = true
 
+        // TODO(b/238425913): We should log this visibility state.
+        @StatusBarIconView.VisibleState
+        val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
-                        // TODO(b/238425913): Right now, if !isVisible, there's just an empty space
-                        //  where the wifi icon would be. We need to pipe isVisible through to
-                        //   [ModernStatusBarWifiView.isIconVisible], which is what actually makes
-                        //   the view GONE.
+                    visibilityState.collect { visibilityState ->
+                        groupView.isVisible = visibilityState == STATE_ICON
+                        dotView.isVisible = visibilityState == STATE_DOT
+                    }
+                }
+
+                launch {
+                    viewModel.wifiIcon.collect { wifiIcon ->
                         view.isVisible = wifiIcon != null
-                        wifiIcon?.let {
-                            IconViewBinder.bind(wifiIcon, iconView)
-                        }
+                        wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
                     }
                 }
 
                 launch {
                     viewModel.tint.collect { tint ->
-                        iconView.imageTintList = ColorStateList.valueOf(tint)
+                        val tintList = ColorStateList.valueOf(tint)
+                        iconView.imageTintList = tintList
+                        activityInView.imageTintList = tintList
+                        activityOutView.imageTintList = tintList
+                        dotView.setDecorColor(tint)
+                    }
+                }
+
+                launch {
+                    viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
+                        activityInView.isVisible = visible
+                    }
+                }
+
+                launch {
+                    viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
+                        activityOutView.isVisible = visible
+                    }
+                }
+
+                launch {
+                    viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible ->
+                        activityContainerView.isVisible = visible
                     }
                 }
             }
         }
 
-        // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
+        return object : Binding {
+            override fun getShouldIconBeVisible(): Boolean {
+                return viewModel.wifiIcon.value != null
+            }
+
+            override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
+                visibilityState.value = state
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index c14a897..0cd9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -19,10 +19,14 @@
 import android.content.Context
 import android.graphics.Rect
 import android.util.AttributeSet
+import android.view.Gravity
 import android.view.LayoutInflater
 import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 
@@ -33,9 +37,20 @@
 class ModernStatusBarWifiView(
     context: Context,
     attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
 
     private lateinit var slot: String
+    private lateinit var binding: WifiViewBinder.Binding
+
+    @StatusBarIconView.VisibleState
+    private var iconVisibleState: Int = STATE_HIDDEN
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            binding.onVisibilityStateChanged(value)
+        }
 
     override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
         // TODO(b/238425913)
@@ -51,42 +66,64 @@
         // TODO(b/238425913)
     }
 
-    override fun setVisibleState(state: Int, animate: Boolean) {
-        // TODO(b/238425913)
+    override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
+        iconVisibleState = state
     }
 
+    @StatusBarIconView.VisibleState
     override fun getVisibleState(): Int {
-        // TODO(b/238425913)
-        return STATE_ICON
+        return iconVisibleState
     }
 
     override fun isIconVisible(): Boolean {
-        // TODO(b/238425913)
-        return true
+        return binding.getShouldIconBeVisible()
     }
 
-    /** Set the slot name for this view. */
-    private fun setSlot(slotName: String) {
-        this.slot = slotName
+    private fun initView(
+        slotName: String,
+        wifiViewModel: WifiViewModel,
+        location: StatusBarLocation,
+    ) {
+        slot = slotName
+        initDotView()
+        binding = WifiViewBinder.bind(this, wifiViewModel, location)
+    }
+
+    // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
+    private fun initDotView() {
+        // TODO(b/238425913): Could we just have this dot view be part of
+        //   R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
+        //   manually? Would that not work with animations?
+        val dotView = StatusBarIconView(mContext, slot, null).also {
+            it.id = R.id.status_bar_dot
+            // Hard-code this view to always be in the DOT state so that whenever it's visible it
+            // will show a dot
+            it.visibleState = STATE_DOT
+        }
+
+        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+        val lp = LayoutParams(width, width)
+        lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+        addView(dotView, lp)
     }
 
     companion object {
         /**
-         * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
+         * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
          * returns it.
          */
         @JvmStatic
         fun constructAndBind(
             context: Context,
             slot: String,
-            viewModel: WifiViewModel,
+            wifiViewModel: WifiViewModel,
+            location: StatusBarLocation,
         ): ModernStatusBarWifiView {
             return (
                 LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
                     as ModernStatusBarWifiView
                 ).also {
-                    it.setSlot(slot)
-                    WifiViewBinder.bind(it, viewModel)
+                    it.initView(slot, wifiViewModel, location)
                 }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
new file mode 100644
index 0000000..40f948f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
+ * showing the shade, so the user is on the home-screen, or in an app).
+ */
+class HomeWifiViewModel(
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    wifiIcon: StateFlow<Icon.Resource?>,
+    isActivityInViewVisible: Flow<Boolean>,
+    isActivityOutViewVisible: Flow<Boolean>,
+    isActivityContainerVisible: Flow<Boolean>,
+) :
+    LocationBasedWifiViewModel(
+        statusBarPipelineFlags,
+        debugTint = Color.CYAN,
+        wifiIcon,
+        isActivityInViewVisible,
+        isActivityOutViewVisible,
+        isActivityContainerVisible,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
new file mode 100644
index 0000000..9642ac4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -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.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown on keyguard (lockscreen). */
+class KeyguardWifiViewModel(
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    wifiIcon: StateFlow<Icon.Resource?>,
+    isActivityInViewVisible: Flow<Boolean>,
+    isActivityOutViewVisible: Flow<Boolean>,
+    isActivityContainerVisible: Flow<Boolean>,
+) :
+    LocationBasedWifiViewModel(
+        statusBarPipelineFlags,
+        debugTint = Color.MAGENTA,
+        wifiIcon,
+        isActivityInViewVisible,
+        isActivityOutViewVisible,
+        isActivityContainerVisible,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
new file mode 100644
index 0000000..e23f8c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for a wifi icon in a specific location. This allows us to control parameters that
+ * are location-specific (for example, different tints of the icon in different locations).
+ *
+ * Must be subclassed for each distinct location.
+ */
+abstract class LocationBasedWifiViewModel(
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    debugTint: Int,
+
+    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+    val wifiIcon: StateFlow<Icon.Resource?>,
+
+    /** True if the activity in view should be visible. */
+    val isActivityInViewVisible: Flow<Boolean>,
+
+    /** True if the activity out view should be visible. */
+    val isActivityOutViewVisible: Flow<Boolean>,
+
+    /** True if the activity container view should be visible. */
+    val isActivityContainerVisible: Flow<Boolean>,
+) {
+    /** The color that should be used to tint the icon. */
+    val tint: Flow<Int> =
+        flowOf(
+            if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+                debugTint
+            } else {
+                DEFAULT_TINT
+            }
+        )
+
+    companion object {
+        /**
+         * A default icon tint.
+         *
+         * TODO(b/238425913): The tint is actually controlled by
+         * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
+         * should use that logic instead of white as a default.
+         */
+        private const val DEFAULT_TINT = Color.WHITE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
new file mode 100644
index 0000000..0ddf90e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -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.systemui.statusbar.pipeline.wifi.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
+class QsWifiViewModel(
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    wifiIcon: StateFlow<Icon.Resource?>,
+    isActivityInViewVisible: Flow<Boolean>,
+    isActivityOutViewVisible: Flow<Boolean>,
+    isActivityContainerVisible: Flow<Boolean>,
+) :
+    LocationBasedWifiViewModel(
+        statusBarPipelineFlags,
+        debugTint = Color.GREEN,
+        wifiIcon,
+        isActivityInViewVisible,
+        isActivityOutViewVisible,
+        isActivityContainerVisible,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 3c243ac..ebbd77b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.content.Context
-import android.graphics.Color
 import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
@@ -26,107 +25,185 @@
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 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.emptyFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Models the UI state for the status bar wifi icon.
+ *
+ * This class exposes three view models, one per status bar location:
+ *  - [home]
+ *  - [keyguard]
+ *  - [qs]
+ *  In order to get the UI state for the wifi icon, you must use one of those view models (whichever
+ *  is correct for your location).
+ *
+ * Internally, this class maintains the current state of the wifi icon and notifies those three
+ * view models of any changes.
  */
-class WifiViewModel @Inject constructor(
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    private val constants: WifiConstants,
+@SysUISingleton
+class WifiViewModel
+@Inject
+constructor(
+    connectivityConstants: ConnectivityConstants,
     private val context: Context,
-    private val logger: ConnectivityPipelineLogger,
-    private val interactor: WifiInteractor,
+    logger: ConnectivityPipelineLogger,
+    interactor: WifiInteractor,
+    @Application private val scope: CoroutineScope,
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    wifiConstants: WifiConstants,
 ) {
     /**
-     * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
+     * Returns the drawable resource ID to use for the wifi icon based on the given network.
+     * Null if we can't compute the icon.
      */
     @DrawableRes
-    private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
-        when (it) {
+    private fun WifiNetworkModel.iconResId(): Int? {
+        return when (this) {
             is WifiNetworkModel.CarrierMerged -> null
             is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
             is WifiNetworkModel.Active ->
                 when {
-                    it.level == null -> null
-                    it.isValidated -> WIFI_FULL_ICONS[it.level]
-                    else -> WIFI_NO_INTERNET_ICONS[it.level]
+                    this.level == null -> null
+                    this.isValidated -> WIFI_FULL_ICONS[this.level]
+                    else -> WIFI_NO_INTERNET_ICONS[this.level]
                 }
         }
     }
 
-    /** The content description for the wifi icon. */
-    private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
-        when (it) {
+    /**
+     * Returns the content description for the wifi icon based on the given network.
+     * Null if we can't compute the content description.
+     */
+    private fun WifiNetworkModel.contentDescription(): ContentDescription? {
+        return when (this) {
             is WifiNetworkModel.CarrierMerged -> null
             is WifiNetworkModel.Inactive ->
                 ContentDescription.Loaded(
                     "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
                 )
             is WifiNetworkModel.Active ->
-                when (it.level) {
+                when (this.level) {
                     null -> null
                     else -> {
-                        val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
+                        val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
                         when {
-                            it.isValidated -> ContentDescription.Loaded(levelDesc)
-                            else -> ContentDescription.Loaded(
-                                "$levelDesc,${context.getString(NO_INTERNET)}"
-                            )
+                            this.isValidated -> ContentDescription.Loaded(levelDesc)
+                            else ->
+                                ContentDescription.Loaded(
+                                    "$levelDesc,${context.getString(NO_INTERNET)}"
+                                )
                         }
                     }
                 }
         }
     }
 
-    /**
-     * The wifi icon that should be displayed. Null if we shouldn't display any icon.
-     */
-    val wifiIcon: Flow<Icon?> = combine(
+    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+    private val wifiIcon: StateFlow<Icon.Resource?> =
+        combine(
+            interactor.isEnabled,
             interactor.isForceHidden,
-            iconResId,
-            contentDescription,
-        ) { isForceHidden, iconResId, contentDescription ->
-            when {
-                isForceHidden ||
-                    iconResId == null ||
-                    iconResId <= 0 -> null
-                else -> Icon.Resource(iconResId, contentDescription)
+            interactor.wifiNetwork,
+        ) { isEnabled, isForceHidden, wifiNetwork ->
+            if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
+                return@combine null
+            }
+
+            val iconResId = wifiNetwork.iconResId() ?: return@combine null
+            val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+
+            return@combine when {
+                wifiConstants.alwaysShowIconIfEnabled -> icon
+                !connectivityConstants.hasDataCapabilities -> icon
+                wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
+                else -> null
             }
         }
+        .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
 
-    /**
-     * True if the activity in icon should be displayed and false otherwise.
-     */
-    val isActivityInVisible: Flow<Boolean>
-        get() =
-            if (!constants.shouldShowActivityConfig) {
-                flowOf(false)
-            } else {
-                interactor.hasActivityIn
+    /** The wifi activity status. Null if we shouldn't display the activity status. */
+    private val activity: Flow<WifiActivityModel?> =
+        if (!wifiConstants.shouldShowActivityConfig) {
+            flowOf(null)
+        } else {
+            combine(interactor.activity, interactor.ssid) { activity, ssid ->
+                when (ssid) {
+                    null -> null
+                    else -> activity
+                }
             }
-                .logOutputChange(logger, "activityInVisible")
+        }
+        .distinctUntilChanged()
+        .logOutputChange(logger, "activity")
+        .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
 
-    /** The tint that should be applied to the icon. */
-    val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
-        emptyFlow()
-    } else {
-        flowOf(Color.CYAN)
-    }
+    private val isActivityInViewVisible: Flow<Boolean> =
+         activity
+             .map { it?.hasActivityIn == true }
+             .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    private val isActivityOutViewVisible: Flow<Boolean> =
+       activity
+           .map { it?.hasActivityOut == true }
+           .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    private val isActivityContainerVisible: Flow<Boolean> =
+         combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
+                    activityIn || activityOut
+                }
+             .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    /** A view model for the status bar on the home screen. */
+    val home: HomeWifiViewModel =
+        HomeWifiViewModel(
+            statusBarPipelineFlags,
+            wifiIcon,
+            isActivityInViewVisible,
+            isActivityOutViewVisible,
+            isActivityContainerVisible,
+        )
+
+    /** A view model for the status bar on keyguard. */
+    val keyguard: KeyguardWifiViewModel =
+        KeyguardWifiViewModel(
+            statusBarPipelineFlags,
+            wifiIcon,
+            isActivityInViewVisible,
+            isActivityOutViewVisible,
+            isActivityContainerVisible,
+        )
+
+    /** A view model for the status bar in quick settings. */
+    val qs: QsWifiViewModel =
+        QsWifiViewModel(
+            statusBarPipelineFlags,
+            wifiIcon,
+            isActivityInViewVisible,
+            isActivityOutViewVisible,
+            isActivityContainerVisible,
+        )
 
     companion object {
         @StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
new file mode 100644
index 0000000..5b2d695
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -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.systemui.statusbar.policy
+
+import android.content.Context
+import android.graphics.ColorFilter
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.widget.BaseAdapter
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserRecordName
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper.getUserSwitcherActionIconResourceId
+import java.lang.ref.WeakReference
+
+/** Provides views for user switcher experiences. */
+abstract class BaseUserSwitcherAdapter
+protected constructor(
+    protected val controller: UserSwitcherController,
+) : BaseAdapter() {
+
+    protected open val users: ArrayList<UserRecord>
+        get() = controller.users
+
+    init {
+        controller.addAdapter(WeakReference(this))
+    }
+
+    override fun getCount(): Int {
+        return if (controller.isKeyguardShowing) {
+            users.count { !it.isRestricted }
+        } else {
+            users.size
+        }
+    }
+
+    override fun getItem(position: Int): UserRecord {
+        return users[position]
+    }
+
+    override fun getItemId(position: Int): Long {
+        return position.toLong()
+    }
+
+    /**
+     * Notifies that a user item in the UI has been clicked.
+     *
+     * If the user switcher is hosted in a dialog, passing a non-null [dialogShower] will allow
+     * animation to and from the parent dialog.
+     */
+    @JvmOverloads
+    fun onUserListItemClicked(
+        record: UserRecord,
+        dialogShower: DialogShower? = null,
+    ) {
+        controller.onUserListItemClicked(record, dialogShower)
+    }
+
+    open fun getName(context: Context, item: UserRecord): String {
+        return getName(context, item, false)
+    }
+
+    /** Returns the name for the given {@link UserRecord}. */
+    open fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
+        return getUserRecordName(
+            context = context,
+            record = item,
+            isGuestUserAutoCreated = controller.isGuestUserAutoCreated,
+            isGuestUserResetting = controller.isGuestUserResetting,
+            isTablet = isTablet,
+        )
+    }
+
+    fun refresh() {
+        controller.refreshUsers(UserHandle.USER_NULL)
+    }
+
+    companion object {
+        @JvmStatic
+        protected val disabledUserAvatarColorFilter: ColorFilter by lazy {
+            val matrix = ColorMatrix()
+            matrix.setSaturation(0f) // 0 - grayscale
+            ColorMatrixColorFilter(matrix)
+        }
+
+        @JvmStatic
+        @JvmOverloads
+        protected fun getIconDrawable(
+            context: Context,
+            item: UserRecord,
+            isTablet: Boolean = false,
+        ): Drawable {
+            val iconRes =
+                getUserSwitcherActionIconResourceId(
+                    item.isAddUser,
+                    item.isGuest,
+                    item.isAddSupervisedUser,
+                    isTablet,
+                )
+            return checkNotNull(context.getDrawable(iconRes))
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 16306081..dc73d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -69,7 +69,7 @@
     private final Context mContext;
     private Resources mResources;
     private final UserSwitcherController mUserSwitcherController;
-    private UserSwitcherController.BaseUserAdapter mAdapter;
+    private BaseUserSwitcherAdapter mAdapter;
     private final KeyguardStateController mKeyguardStateController;
     private final FalsingManager mFalsingManager;
     protected final SysuiStatusBarStateController mStatusBarStateController;
@@ -171,7 +171,7 @@
         mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
         mUserAvatarViewWithBackground = mView.findViewById(
                 R.id.kg_multi_user_avatar_with_background);
-        mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
+        mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
             @Override
             public View getView(int position, View convertView, ViewGroup parent) {
                 return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e2f5734..712953e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -232,14 +229,8 @@
     }
 
     /**
-     * See:
+     * Returns {@code true} if the user switcher should be open by default on the lock screen.
      *
-     * <ul>
-     *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
-     *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
-     * </ul>
-     *
-     * @return true if the user switcher should be open by default on the lock screen.
      * @see android.os.UserManager#isUserSwitcherEnabled()
      */
     public boolean isSimpleUserSwitcher() {
@@ -436,7 +427,7 @@
     }
 
     static class KeyguardUserAdapter extends
-            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+            BaseUserSwitcherAdapter implements View.OnClickListener {
 
         private final Context mContext;
         private final Resources mResources;
@@ -514,9 +505,9 @@
                 v.bind(name, drawable, item.info.id);
             }
             v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
+            v.setDisabledByAdmin(item.isDisabledByAdmin());
             v.setEnabled(item.isSwitchToEnabled);
-            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+            UserSwitcherController.setSelectableAlpha(v);
 
             if (item.isCurrent) {
                 mCurrentUserView = v;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a33603..da6d455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -221,8 +221,10 @@
 
         mEditText.setTextColor(textColor);
         mEditText.setHintTextColor(hintColor);
-        mEditText.getTextCursorDrawable().setColorFilter(
-                accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        if (mEditText.getTextCursorDrawable() != null) {
+            mEditText.getTextCursorDrawable().setColorFilter(
+                    accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        }
         mContentBackground.setColor(editBgColor);
         mContentBackground.setStroke(stroke, accentColor);
         mDelete.setImageTintList(ColorStateList.valueOf(deleteFgColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
new file mode 100644
index 0000000..146b222
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.statusbar.policy
+
+import android.annotation.UserIdInt
+import android.content.Intent
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import java.lang.ref.WeakReference
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for a class that provides user switching functionality and state. */
+interface UserSwitcherController : Dumpable {
+
+    /** The current list of [UserRecord]. */
+    val users: ArrayList<UserRecord>
+
+    /** Whether the user switcher experience should use the simple experience. */
+    val isSimpleUserSwitcher: Boolean
+
+    /** Require a view for jank detection */
+    fun init(view: View)
+
+    /** The [UserRecord] of the current user or `null` when none. */
+    val currentUserRecord: UserRecord?
+
+    /** The name of the current user of the device or `null`, when none is selected. */
+    val currentUserName: String?
+
+    /**
+     * Notifies that a user has been selected.
+     *
+     * This will trigger the right user journeys to create a guest user, switch users, and/or
+     * navigate to the correct destination.
+     *
+     * If a user with the given ID is not found, this method is a no-op.
+     *
+     * @param userId The ID of the user to switch to.
+     * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
+     */
+    fun onUserSelected(userId: Int, dialogShower: DialogShower?)
+
+    /** Whether it is allowed to add users while the device is locked. */
+    val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+
+    /** Whether the guest user is configured to always be present on the device. */
+    val isGuestUserAutoCreated: Boolean
+
+    /** Whether the guest user is currently being reset. */
+    val isGuestUserResetting: Boolean
+
+    /** Creates and switches to the guest user. */
+    fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
+
+    /** Shows the add user dialog. */
+    fun showAddUserDialog(dialogShower: DialogShower?)
+
+    /** Starts an activity to add a supervised user to the device. */
+    fun startSupervisedUserActivity()
+
+    /** Notifies when the display density or font scale has changed. */
+    fun onDensityOrFontScaleChanged()
+
+    /** Registers an adapter to notify when the users change. */
+    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+
+    /** Notifies the item for a user has been clicked. */
+    fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+
+    /**
+     * Removes guest user and switches to target user. The guest must be the current user and its id
+     * must be `guestUserId`.
+     *
+     * If `targetUserId` is `UserHandle.USER_NULL`, then create a new guest user in the foreground,
+     * and immediately switch to it. This is used for wiping the current guest and replacing it with
+     * a new one.
+     *
+     * If `targetUserId` is specified, then remove the guest in the background while switching to
+     * `targetUserId`.
+     *
+     * If device is configured with `config_guestUserAutoCreated`, then after guest user is removed,
+     * a new one is created in the background. This has no effect if `targetUserId` is
+     * `UserHandle.USER_NULL`.
+     *
+     * @param guestUserId id of the guest user to remove
+     * @param targetUserId id of the user to switch to after guest is removed. If
+     * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
+     */
+    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+
+    /**
+     * Exits guest user and switches to previous non-guest user. The guest must be the current user.
+     *
+     * @param guestUserId user id of the guest user to exit
+     * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when
+     * target user id is not known
+     * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
+     * only if its ephemeral, else keep guest
+     */
+    fun exitGuestUser(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean
+    )
+
+    /**
+     * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
+     * observer to wait until the device is provisioned, then schedule the guest creation.
+     */
+    fun schedulePostBootGuestCreation()
+
+    /** Whether keyguard is showing. */
+    val isKeyguardShowing: Boolean
+
+    /** Starts an activity with the given [Intent]. */
+    fun startActivity(intent: Intent)
+
+    /**
+     * Refreshes users from UserManager.
+     *
+     * The pictures are only loaded if they have not been loaded yet.
+     *
+     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
+     */
+    fun refreshUsers(forcePictureLoadForId: Int)
+
+    /** Adds a subscriber to when user switches. */
+    fun addUserSwitchCallback(callback: UserSwitchCallback)
+
+    /** Removes a previously-added subscriber. */
+    fun removeUserSwitchCallback(callback: UserSwitchCallback)
+
+    /** Defines interface for classes that can be called back when the user is switched. */
+    fun interface UserSwitchCallback {
+        /** Notifies that the user has switched. */
+        fun onUserSwitched()
+    }
+
+    companion object {
+        /** Alpha value to apply to a user view in the user switcher when it's selectable. */
+        private const val ENABLED_ALPHA =
+            LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA
+
+        /** Alpha value to apply to a user view in the user switcher when it's not selectable. */
+        private const val DISABLED_ALPHA =
+            LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA
+
+        @JvmStatic
+        fun setSelectableAlpha(view: View) {
+            view.alpha =
+                if (view.isEnabled) {
+                    ENABLED_ALPHA
+                } else {
+                    DISABLED_ALPHA
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
new file mode 100644
index 0000000..1692656
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.policy
+
+import android.content.Context
+import android.content.Intent
+import android.view.View
+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.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Implementation of [UserSwitcherController]. */
+@SysUISingleton
+class UserSwitcherControllerImpl
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    flags: FeatureFlags,
+    @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
+    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val activityStarter: ActivityStarter,
+) : UserSwitcherController {
+
+    private val useInteractor: Boolean =
+        flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
+            !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+    private val _oldImpl: UserSwitcherControllerOldImpl
+        get() = oldImpl.get()
+    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+    private val callbackCompatMap =
+        mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
+
+    private fun notSupported(): Nothing {
+        error("Not supported in the new implementation!")
+    }
+
+    override val users: ArrayList<UserRecord>
+        get() =
+            if (useInteractor) {
+                userInteractor.userRecords.value
+            } else {
+                _oldImpl.users
+            }
+
+    override val isSimpleUserSwitcher: Boolean
+        get() =
+            if (useInteractor) {
+                userInteractor.isSimpleUserSwitcher
+            } else {
+                _oldImpl.isSimpleUserSwitcher
+            }
+
+    override fun init(view: View) {
+        if (!useInteractor) {
+            _oldImpl.init(view)
+        }
+    }
+
+    override val currentUserRecord: UserRecord?
+        get() =
+            if (useInteractor) {
+                userInteractor.selectedUserRecord.value
+            } else {
+                _oldImpl.currentUserRecord
+            }
+
+    override val currentUserName: String?
+        get() =
+            if (useInteractor) {
+                currentUserRecord?.let {
+                    LegacyUserUiHelper.getUserRecordName(
+                        context = applicationContext,
+                        record = it,
+                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+                        isGuestUserResetting = userInteractor.isGuestUserResetting,
+                    )
+                }
+            } else {
+                _oldImpl.currentUserName
+            }
+
+    override fun onUserSelected(
+        userId: Int,
+        dialogShower: UserSwitchDialogController.DialogShower?
+    ) {
+        if (useInteractor) {
+            userInteractor.selectUser(userId)
+        } else {
+            _oldImpl.onUserSelected(userId, dialogShower)
+        }
+    }
+
+    override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+        get() =
+            if (useInteractor) {
+                notSupported()
+            } else {
+                _oldImpl.isAddUsersFromLockScreenEnabled
+            }
+
+    override val isGuestUserAutoCreated: Boolean
+        get() =
+            if (useInteractor) {
+                userInteractor.isGuestUserAutoCreated
+            } else {
+                _oldImpl.isGuestUserAutoCreated
+            }
+
+    override val isGuestUserResetting: Boolean
+        get() =
+            if (useInteractor) {
+                userInteractor.isGuestUserResetting
+            } else {
+                _oldImpl.isGuestUserResetting
+            }
+
+    override fun createAndSwitchToGuestUser(
+        dialogShower: UserSwitchDialogController.DialogShower?,
+    ) {
+        if (useInteractor) {
+            notSupported()
+        } else {
+            _oldImpl.createAndSwitchToGuestUser(dialogShower)
+        }
+    }
+
+    override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
+        if (useInteractor) {
+            notSupported()
+        } else {
+            _oldImpl.showAddUserDialog(dialogShower)
+        }
+    }
+
+    override fun startSupervisedUserActivity() {
+        if (useInteractor) {
+            notSupported()
+        } else {
+            _oldImpl.startSupervisedUserActivity()
+        }
+    }
+
+    override fun onDensityOrFontScaleChanged() {
+        if (!useInteractor) {
+            _oldImpl.onDensityOrFontScaleChanged()
+        }
+    }
+
+    override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+        if (useInteractor) {
+            userInteractor.addCallback(
+                object : UserInteractor.UserCallback {
+                    override fun isEvictable(): Boolean {
+                        return adapter.get() == null
+                    }
+
+                    override fun onUserStateChanged() {
+                        adapter.get()?.notifyDataSetChanged()
+                    }
+                }
+            )
+        } else {
+            _oldImpl.addAdapter(adapter)
+        }
+    }
+
+    override fun onUserListItemClicked(
+        record: UserRecord,
+        dialogShower: UserSwitchDialogController.DialogShower?,
+    ) {
+        if (useInteractor) {
+            if (LegacyUserDataHelper.isUser(record)) {
+                userInteractor.selectUser(record.resolveId())
+            } else {
+                userInteractor.executeAction(LegacyUserDataHelper.toUserActionModel(record))
+            }
+        } else {
+            _oldImpl.onUserListItemClicked(record, dialogShower)
+        }
+    }
+
+    override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+        if (useInteractor) {
+            userInteractor.removeGuestUser(
+                guestUserId = guestUserId,
+                targetUserId = targetUserId,
+            )
+        } else {
+            _oldImpl.removeGuestUser(guestUserId, targetUserId)
+        }
+    }
+
+    override fun exitGuestUser(
+        guestUserId: Int,
+        targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean
+    ) {
+        if (useInteractor) {
+            userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+        } else {
+            _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+        }
+    }
+
+    override fun schedulePostBootGuestCreation() {
+        if (useInteractor) {
+            guestUserInteractor.onDeviceBootCompleted()
+        } else {
+            _oldImpl.schedulePostBootGuestCreation()
+        }
+    }
+
+    override val isKeyguardShowing: Boolean
+        get() =
+            if (useInteractor) {
+                keyguardInteractor.isKeyguardShowing()
+            } else {
+                _oldImpl.isKeyguardShowing
+            }
+
+    override fun startActivity(intent: Intent) {
+        if (useInteractor) {
+            activityStarter.startActivity(intent, /* dismissShade= */ false)
+        } else {
+            _oldImpl.startActivity(intent)
+        }
+    }
+
+    override fun refreshUsers(forcePictureLoadForId: Int) {
+        if (useInteractor) {
+            userInteractor.refreshUsers()
+        } else {
+            _oldImpl.refreshUsers(forcePictureLoadForId)
+        }
+    }
+
+    override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
+        if (useInteractor) {
+            val interactorCallback =
+                object : UserInteractor.UserCallback {
+                    override fun onUserStateChanged() {
+                        callback.onUserSwitched()
+                    }
+                }
+            callbackCompatMap[callback] = interactorCallback
+            userInteractor.addCallback(interactorCallback)
+        } else {
+            _oldImpl.addUserSwitchCallback(callback)
+        }
+    }
+
+    override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
+        if (useInteractor) {
+            val interactorCallback = callbackCompatMap.remove(callback)
+            if (interactorCallback != null) {
+                userInteractor.removeCallback(interactorCallback)
+            }
+        } else {
+            _oldImpl.removeUserSwitchCallback(callback)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        if (useInteractor) {
+            userInteractor.dump(pw)
+        } else {
+            _oldImpl.dump(pw, args)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index 63e88a6..46d2f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -11,33 +11,24 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.policy;
 
 import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
 
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -45,28 +36,22 @@
 import android.provider.Settings;
 import android.telephony.TelephonyCallback;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.View;
 import android.view.WindowManagerGlobal;
-import android.widget.BaseAdapter;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
-import androidx.collection.SimpleArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.Dumpable;
 import com.android.systemui.GuestResetOrExitSessionReceiver;
 import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.R;
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -82,11 +67,12 @@
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.CreateUserActivity;
 import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper;
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
+import com.android.systemui.user.shared.model.UserActionModel;
+import com.android.systemui.user.ui.dialog.AddUserDialog;
+import com.android.systemui.user.ui.dialog.ExitGuestDialog;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -106,15 +92,14 @@
 import kotlinx.coroutines.flow.StateFlowKt;
 
 /**
- * Keeps a list of all users on the device for user switching.
+ * Old implementation. Keeps a list of all users on the device for user switching.
+ *
+ * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
+ * instead.
  */
+@Deprecated
 @SysUISingleton
-public class UserSwitcherController implements Dumpable {
-
-    public static final float USER_SWITCH_ENABLED_ALPHA =
-            LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA;
-    public static final float USER_SWITCH_DISABLED_ALPHA =
-            LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA;
+public class UserSwitcherControllerOldImpl implements UserSwitcherController {
 
     private static final String TAG = "UserSwitcherController";
     private static final boolean DEBUG = false;
@@ -123,7 +108,7 @@
     private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
 
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-    private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l;
+    private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
 
     private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
     private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
@@ -132,7 +117,7 @@
     protected final UserTracker mUserTracker;
     protected final UserManager mUserManager;
     private final ContentObserver mSettingsObserver;
-    private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
+    private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
     @VisibleForTesting
     final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
     @VisibleForTesting
@@ -148,9 +133,6 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
-    private final SimpleArrayMap<UserRecord, EnforcedAdmin> mEnforcedAdminByUserRecord =
-            new SimpleArrayMap<>();
-    private final ArraySet<UserRecord> mDisabledByAdmin = new ArraySet<>();
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     @VisibleForTesting
@@ -158,7 +140,6 @@
     @VisibleForTesting
     Dialog mAddUserDialog;
     private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
-    private boolean mResumeUserOnGuestLogout = true;
     private boolean mSimpleUserSwitcher;
     // When false, there won't be any visual affordance to add a new user from the keyguard even if
     // the user is unlocked
@@ -187,7 +168,8 @@
             Collections.synchronizedList(new ArrayList<>());
 
     @Inject
-    public UserSwitcherController(Context context,
+    public UserSwitcherControllerOldImpl(
+            Context context,
             IActivityManager activityManager,
             UserManager userManager,
             UserTracker userTracker,
@@ -222,9 +204,9 @@
         mFalsingManager = falsingManager;
         mInteractionJankMonitor = interactionJankMonitor;
         mLatencyTracker = latencyTracker;
+        mGlobalSettings = globalSettings;
         mGuestResumeSessionReceiver = guestResumeSessionReceiver;
         mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
-        mGlobalSettings = globalSettings;
         mBgExecutor = bgExecutor;
         mLongRunningExecutor = longRunningExecutor;
         mUiExecutor = uiExecutor;
@@ -303,16 +285,10 @@
         refreshUsers(UserHandle.USER_NULL);
     }
 
-    /**
-     * Refreshes users from UserManager.
-     *
-     * The pictures are only loaded if they have not been loaded yet.
-     *
-     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
-     */
+    @Override
     @SuppressWarnings("unchecked")
-    private void refreshUsers(int forcePictureLoadForId) {
-        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId+")");
+    public void refreshUsers(int forcePictureLoadForId) {
+        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
         if (forcePictureLoadForId != UserHandle.USER_NULL) {
             mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
         }
@@ -323,8 +299,8 @@
 
         boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
         SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
-        final int N = mUsers.size();
-        for (int i = 0; i < N; i++) {
+        final int userCount = mUsers.size();
+        for (int i = 0; i < userCount; i++) {
             UserRecord r = mUsers.get(i);
             if (r == null || r.picture == null || r.info == null || forceAllUsers
                     || mForcePictureLoadForUserId.get(r.info.id)) {
@@ -349,7 +325,6 @@
 
             for (UserInfo info : infos) {
                 boolean isCurrent = currentId == info.id;
-                boolean switchToEnabled = canSwitchUsers || isCurrent;
                 if (!mUserSwitcherEnabled && !info.isPrimary()) {
                     continue;
                 }
@@ -358,25 +333,22 @@
                     if (info.isGuest()) {
                         // Tapping guest icon triggers remove and a user switch therefore
                         // the icon shouldn't be enabled even if the user is current
-                        guestRecord = new UserRecord(info, null /* picture */,
-                                true /* isGuest */, isCurrent, false /* isAddUser */,
-                                false /* isRestricted */, canSwitchUsers,
-                                false /* isAddSupervisedUser */);
+                        guestRecord = LegacyUserDataHelper.createRecord(
+                                mContext,
+                                mUserManager,
+                                null /* picture */,
+                                info,
+                                isCurrent,
+                                canSwitchUsers);
                     } else if (info.supportsSwitchToByUser()) {
-                        Bitmap picture = bitmaps.get(info.id);
-                        if (picture == null) {
-                            picture = mUserManager.getUserIcon(info.id);
-
-                            if (picture != null) {
-                                int avatarSize = mContext.getResources()
-                                        .getDimensionPixelSize(R.dimen.max_avatar_size);
-                                picture = Bitmap.createScaledBitmap(
-                                        picture, avatarSize, avatarSize, true);
-                            }
-                        }
-                        records.add(new UserRecord(info, picture, false /* isGuest */,
-                                isCurrent, false /* isAddUser */, false /* isRestricted */,
-                                switchToEnabled, false /* isAddSupervisedUser */));
+                        records.add(
+                                LegacyUserDataHelper.createRecord(
+                                        mContext,
+                                        mUserManager,
+                                        bitmaps.get(info.id),
+                                        info,
+                                        isCurrent,
+                                        canSwitchUsers));
                     }
                 }
             }
@@ -387,18 +359,20 @@
                     // we will just use it as an indicator for "Resetting guest...".
                     // Otherwise, default to canSwitchUsers.
                     boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
-                    guestRecord = new UserRecord(null /* info */, null /* picture */,
-                            true /* isGuest */, false /* isCurrent */,
-                            false /* isAddUser */, false /* isRestricted */,
-                            isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
-                    checkIfAddUserDisallowedByAdminOnly(guestRecord);
+                    guestRecord = LegacyUserDataHelper.createRecord(
+                            mContext,
+                            currentId,
+                            UserActionModel.ENTER_GUEST_MODE,
+                            false /* isRestricted */,
+                            isSwitchToGuestEnabled);
                     records.add(guestRecord);
                 } else if (canCreateGuest(guestRecord != null)) {
-                    guestRecord = new UserRecord(null /* info */, null /* picture */,
-                            true /* isGuest */, false /* isCurrent */,
-                            false /* isAddUser */, createIsRestricted(), canSwitchUsers,
-                            false /* isAddSupervisedUser */);
-                    checkIfAddUserDisallowedByAdminOnly(guestRecord);
+                    guestRecord = LegacyUserDataHelper.createRecord(
+                            mContext,
+                            currentId,
+                            UserActionModel.ENTER_GUEST_MODE,
+                            false /* isRestricted */,
+                            canSwitchUsers);
                     records.add(guestRecord);
                 }
             } else {
@@ -406,20 +380,23 @@
             }
 
             if (canCreateUser()) {
-                UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
-                        false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
-                        createIsRestricted(), canSwitchUsers,
-                        false /* isAddSupervisedUser */);
-                checkIfAddUserDisallowedByAdminOnly(addUserRecord);
-                records.add(addUserRecord);
+                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+                        mContext,
+                        currentId,
+                        UserActionModel.ADD_USER,
+                        createIsRestricted(),
+                        canSwitchUsers);
+                records.add(userRecord);
             }
 
             if (canCreateSupervisedUser()) {
-                UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
-                        false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
-                        createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
-                checkIfAddUserDisallowedByAdminOnly(addUserRecord);
-                records.add(addUserRecord);
+                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+                        mContext,
+                        currentId,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        createIsRestricted(),
+                        canSwitchUsers);
+                records.add(userRecord);
             }
 
             mUiExecutor.execute(() -> {
@@ -431,38 +408,41 @@
         });
     }
 
-    boolean systemCanCreateUsers() {
+    private boolean systemCanCreateUsers() {
         return !mUserManager.hasBaseUserRestriction(
                 UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
     }
 
-    boolean currentUserCanCreateUsers() {
+    private boolean currentUserCanCreateUsers() {
         UserInfo currentUser = mUserTracker.getUserInfo();
         return currentUser != null
                 && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
                 && systemCanCreateUsers();
     }
 
-    boolean anyoneCanCreateUsers() {
+    private boolean anyoneCanCreateUsers() {
         return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
     }
 
+    @VisibleForTesting
     boolean canCreateGuest(boolean hasExistingGuest) {
         return mUserSwitcherEnabled
                 && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
                 && !hasExistingGuest;
     }
 
+    @VisibleForTesting
     boolean canCreateUser() {
         return mUserSwitcherEnabled
                 && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
                 && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
     }
 
-    boolean createIsRestricted() {
+    private boolean createIsRestricted() {
         return !mAddUsersFromLockScreen.getValue();
     }
 
+    @VisibleForTesting
     boolean canCreateSupervisedUser() {
         return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
     }
@@ -476,7 +456,7 @@
 
     private void notifyAdapters() {
         for (int i = mAdapters.size() - 1; i >= 0; i--) {
-            BaseUserAdapter adapter = mAdapters.get(i).get();
+            BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
             if (adapter != null) {
                 adapter.notifyDataSetChanged();
             } else {
@@ -485,37 +465,20 @@
         }
     }
 
+    @Override
     public boolean isSimpleUserSwitcher() {
         return mSimpleUserSwitcher;
     }
 
-    public void setResumeUserOnGuestLogout(boolean resume) {
-        mResumeUserOnGuestLogout = resume;
-    }
-
     /**
      * Returns whether the current user is a system user.
      */
-    public boolean isSystemUser() {
+    @VisibleForTesting
+    boolean isSystemUser() {
         return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
     }
 
-    public void removeUserId(int userId) {
-        if (userId == UserHandle.USER_SYSTEM) {
-            Log.w(TAG, "User " + userId + " could not removed.");
-            return;
-        }
-        if (mUserTracker.getUserId() == userId) {
-            switchToUserId(UserHandle.USER_SYSTEM);
-        }
-        if (mUserManager.removeUser(userId)) {
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    }
-
-    /**
-     * @return UserRecord for the current user
-     */
+    @Override
     public @Nullable UserRecord getCurrentUserRecord() {
         for (int i = 0; i < mUsers.size(); ++i) {
             UserRecord userRecord = mUsers.get(i);
@@ -526,17 +489,7 @@
         return null;
     }
 
-    /**
-     * Notifies that a user has been selected.
-     *
-     * <p>This will trigger the right user journeys to create a guest user, switch users, and/or
-     * navigate to the correct destination.
-     *
-     * <p>If a user with the given ID is not found, this method is a no-op.
-     *
-     * @param userId The ID of the user to switch to.
-     * @param dialogShower An optional {@link DialogShower} in case we need to show dialogs.
-     */
+    @Override
     public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
         UserRecord userRecord = mUsers.stream()
                 .filter(x -> x.resolveId() == userId)
@@ -549,23 +502,23 @@
         onUserListItemClicked(userRecord, dialogShower);
     }
 
-    /** Whether it is allowed to add users while the device is locked. */
-    public Flow<Boolean> getAddUsersFromLockScreen() {
+    @Override
+    public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
         return mAddUsersFromLockScreen;
     }
 
-    /** Returns {@code true} if the guest user is configured to always be present on the device. */
+    @Override
     public boolean isGuestUserAutoCreated() {
         return mGuestUserAutoCreated;
     }
 
-    /** Returns {@code true} if the guest user is currently being reset. */
+    @Override
     public boolean isGuestUserResetting() {
         return mGuestIsResetting.get();
     }
 
-    @VisibleForTesting
-    void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
+    @Override
+    public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
         if (record.isGuest && record.info == null) {
             createAndSwitchToGuestUser(dialogShower);
         } else if (record.isAddUser) {
@@ -604,7 +557,7 @@
         switchToUserId(id);
     }
 
-    protected void switchToUserId(int id) {
+    private void switchToUserId(int id) {
         try {
             if (mView != null) {
                 mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
@@ -621,7 +574,7 @@
 
     private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
         int newId = UserHandle.USER_SYSTEM;
-        if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+        if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
             UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
             if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
                 newId = info.id;
@@ -630,12 +583,23 @@
         showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
     }
 
-    private void showExitGuestDialog(int id, boolean isGuestEphemeral,
-                        int targetId, DialogShower dialogShower) {
+    private void showExitGuestDialog(
+            int id,
+            boolean isGuestEphemeral,
+            int targetId,
+            DialogShower dialogShower) {
         if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
             mExitGuestDialog.cancel();
         }
-        mExitGuestDialog = new ExitGuestDialog(mContext, id, isGuestEphemeral, targetId);
+        mExitGuestDialog = new ExitGuestDialog(
+                mContext,
+                id,
+                isGuestEphemeral,
+                targetId,
+                mKeyguardStateController.isShowing(),
+                mFalsingManager,
+                mDialogLaunchAnimator,
+                this::exitGuestUser);
         if (dialogShower != null) {
             dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
                     InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
@@ -645,9 +609,7 @@
         }
     }
 
-    /**
-     * Creates and switches to the guest user.
-     */
+    @Override
     public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
         createGuestAsync(guestId -> {
             // guestId may be USER_NULL if we haven't reloaded the user list yet.
@@ -658,14 +620,20 @@
         });
     }
 
-    /**
-     * Shows the add user dialog.
-     */
+    @Override
     public void showAddUserDialog(@Nullable DialogShower dialogShower) {
         if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
             mAddUserDialog.cancel();
         }
-        mAddUserDialog = new AddUserDialog(mContext);
+        final UserInfo currentUser = mUserTracker.getUserInfo();
+        mAddUserDialog = new AddUserDialog(
+                mContext,
+                currentUser.getUserHandle(),
+                mKeyguardStateController.isShowing(),
+                /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
+                mFalsingManager,
+                mBroadcastSender,
+                mDialogLaunchAnimator);
         if (dialogShower != null) {
             dialogShower.showDialog(mAddUserDialog,
                     new DialogCuj(
@@ -677,9 +645,7 @@
         }
     }
 
-    /**
-     * Starts an activity to add a supervised user to the device.
-     */
+    @Override
     public void startSupervisedUserActivity() {
         final Intent intent = new Intent()
                 .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
@@ -711,7 +677,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) {
                 Log.v(TAG, "Broadcast: a=" + intent.getAction()
-                       + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
+                        + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
             }
 
             boolean unpauseRefreshUsers = false;
@@ -725,8 +691,8 @@
 
                 final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 final UserInfo userInfo = mUserManager.getUserInfo(currentId);
-                final int N = mUsers.size();
-                for (int i = 0; i < N; i++) {
+                final int userCount = mUsers.size();
+                for (int i = 0; i < userCount; i++) {
                     UserRecord record = mUsers.get(i);
                     if (record.info == null) continue;
                     boolean shouldBeCurrent = record.info.id == currentId;
@@ -805,7 +771,7 @@
         pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
     }
 
-    /** Returns the name of the current user of the phone. */
+    @Override
     public String getCurrentUserName() {
         if (mUsers.isEmpty()) return null;
         UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
@@ -814,40 +780,22 @@
         return item.info.name;
     }
 
+    @Override
     public void onDensityOrFontScaleChanged() {
         refreshUsers(UserHandle.USER_ALL);
     }
 
-    @VisibleForTesting
-    public void addAdapter(WeakReference<BaseUserAdapter> adapter) {
+    @Override
+    public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
         mAdapters.add(adapter);
     }
 
-    @VisibleForTesting
+    @Override
     public ArrayList<UserRecord> getUsers() {
         return mUsers;
     }
 
-    /**
-     * Removes guest user and switches to target user. The guest must be the current user and its id
-     * must be {@code guestUserId}.
-     *
-     * <p>If {@code targetUserId} is {@link UserHandle#USER_NULL}, then create a new guest user in
-     * the foreground, and immediately switch to it. This is used for wiping the current guest and
-     * replacing it with a new one.
-     *
-     * <p>If {@code targetUserId} is specified, then remove the guest in the background while
-     * switching to {@code targetUserId}.
-     *
-     * <p>If device is configured with {@link
-     * com.android.internal.R.bool.config_guestUserAutoCreated}, then after guest user is removed, a
-     * new one is created in the background. This has no effect if {@code targetUserId} is {@link
-     * UserHandle#USER_NULL}.
-     *
-     * @param guestUserId id of the guest user to remove
-     * @param targetUserId id of the user to switch to after guest is removed. If {@link
-     * UserHandle#USER_NULL}, then switch immediately to the newly created guest user.
-     */
+    @Override
     public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
         UserInfo currentUser = mUserTracker.getUserInfo();
         if (currentUser.id != guestUserId) {
@@ -894,18 +842,9 @@
         }
     }
 
-    /**
-     * Exits guest user and switches to previous non-guest user. The guest must be the current
-     * user.
-     *
-     * @param guestUserId user id of the guest user to exit
-     * @param targetUserId user id of the guest user to exit, set to UserHandle#USER_NULL when
-     *                       target user id is not known
-     * @param forceRemoveGuestOnExit true: remove guest before switching user,
-     *                               false: remove guest only if its ephemeral, else keep guest
-     */
+    @Override
     public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
-                    boolean forceRemoveGuestOnExit) {
+            boolean forceRemoveGuestOnExit) {
         UserInfo currentUser = mUserTracker.getUserInfo();
         if (currentUser.id != guestUserId) {
             Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
@@ -921,7 +860,7 @@
         int newUserId = UserHandle.USER_SYSTEM;
         if (targetUserId == UserHandle.USER_NULL) {
             // when target user is not specified switch to last non guest user
-            if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+            if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
                 UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
                 if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
                     newUserId = info.id;
@@ -959,10 +898,7 @@
 
     }
 
-    /**
-     * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
-     * observer to wait until the device is provisioned, then schedule the guest creation.
-     */
+    @Override
     public void schedulePostBootGuestCreation() {
         if (isDeviceAllowedToAddGuest()) {
             guaranteeGuestPresent();
@@ -1014,7 +950,7 @@
      * @return The multi-user user ID of the newly created guest user, or
      * {@link UserHandle#USER_NULL} if the guest couldn't be created.
      */
-    public @UserIdInt int createGuest() {
+    private @UserIdInt int createGuest() {
         UserInfo guest;
         try {
             guest = mUserManager.createGuest(mContext);
@@ -1029,146 +965,14 @@
         return guest.id;
     }
 
-    /**
-     * Require a view for jank detection
-     */
+    @Override
     public void init(View view) {
         mView = view;
     }
 
-    @VisibleForTesting
-    public KeyguardStateController getKeyguardStateController() {
-        return mKeyguardStateController;
-    }
-
-    /**
-     * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one.
-     */
-    @Nullable
-    public EnforcedAdmin getEnforcedAdmin(UserRecord record) {
-        return mEnforcedAdminByUserRecord.get(record);
-    }
-
-    /**
-     * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise.
-     */
-    public boolean isDisabledByAdmin(UserRecord record) {
-        return mDisabledByAdmin.contains(record);
-    }
-
-    public static abstract class BaseUserAdapter extends BaseAdapter {
-
-        final UserSwitcherController mController;
-        private final KeyguardStateController mKeyguardStateController;
-
-        protected BaseUserAdapter(UserSwitcherController controller) {
-            mController = controller;
-            mKeyguardStateController = controller.getKeyguardStateController();
-            controller.addAdapter(new WeakReference<>(this));
-        }
-
-        protected ArrayList<UserRecord> getUsers() {
-            return mController.getUsers();
-        }
-
-        public int getUserCount() {
-            return countUsers(false);
-        }
-
-        @Override
-        public int getCount() {
-            return countUsers(true);
-        }
-
-        private int countUsers(boolean includeGuest) {
-            boolean keyguardShowing = mKeyguardStateController.isShowing();
-            final int userSize = getUsers().size();
-            int count = 0;
-            for (int i = 0; i < userSize; i++) {
-                if (getUsers().get(i).isGuest && !includeGuest) {
-                    continue;
-                }
-                if (getUsers().get(i).isRestricted && keyguardShowing) {
-                    break;
-                }
-                count++;
-            }
-            return count;
-        }
-
-        @Override
-        public UserRecord getItem(int position) {
-            return getUsers().get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        /**
-         * It handles click events on user list items.
-         *
-         * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower}
-         * will allow animation to and from the parent dialog.
-         *
-         */
-        public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) {
-            mController.onUserListItemClicked(record, dialogShower);
-        }
-
-        public void onUserListItemClicked(UserRecord record) {
-            onUserListItemClicked(record, null);
-        }
-
-        public String getName(Context context, UserRecord item) {
-            return getName(context, item, false);
-        }
-
-        /**
-         * Returns the name for the given {@link UserRecord}.
-         */
-        public String getName(Context context, UserRecord item, boolean isTablet) {
-            return LegacyUserUiHelper.getUserRecordName(
-                    context,
-                    item,
-                    mController.isGuestUserAutoCreated(),
-                    mController.isGuestUserResetting(),
-                    isTablet);
-        }
-
-        protected static ColorFilter getDisabledUserAvatarColorFilter() {
-            ColorMatrix matrix = new ColorMatrix();
-            matrix.setSaturation(0f);   // 0 - grayscale
-            return new ColorMatrixColorFilter(matrix);
-        }
-
-        protected static Drawable getIconDrawable(Context context, UserRecord item) {
-            return getIconDrawable(context, item, false);
-        }
-        protected static Drawable getIconDrawable(Context context, UserRecord item,
-                boolean isTablet) {
-            int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
-                    item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet);
-            return context.getDrawable(iconRes);
-        }
-
-        public void refresh() {
-            mController.refreshUsers(UserHandle.USER_NULL);
-        }
-    }
-
-    private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
-        EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
-                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
-        if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) {
-            mDisabledByAdmin.add(record);
-            mEnforcedAdminByUserRecord.put(record, admin);
-        } else {
-            mDisabledByAdmin.remove(record);
-            mEnforcedAdminByUserRecord.put(record, null);
-        }
+    @Override
+    public boolean isKeyguardShowing() {
+        return mKeyguardStateController.isShowing();
     }
 
     private boolean shouldUseSimpleUserSwitcher() {
@@ -1178,20 +982,17 @@
                 defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
     }
 
+    @Override
     public void startActivity(Intent intent) {
         mActivityStarter.startActivity(intent, true);
     }
 
-    /**
-     *  Add a subscriber to when user switches.
-     */
+    @Override
     public void addUserSwitchCallback(UserSwitchCallback callback) {
         mUserSwitchCallbacks.add(callback);
     }
 
-    /**
-     *  Remove a subscriber to when user switches.
-     */
+    @Override
     public void removeUserSwitchCallback(UserSwitchCallback callback) {
         mUserSwitchCallbacks.remove(callback);
     }
@@ -1218,7 +1019,7 @@
                     // which
                     // helps making the transition faster.
                     if (!mKeyguardStateController.isShowing()) {
-                        mHandler.post(UserSwitcherController.this::notifyAdapters);
+                        mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
                     } else {
                         notifyAdapters();
                     }
@@ -1238,142 +1039,4 @@
                     }
                 }
             };
-
-
-    private final class ExitGuestDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
-
-        private final int mGuestId;
-        private final int mTargetId;
-        private final boolean mIsGuestEphemeral;
-
-        ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
-                    int targetId) {
-            super(context);
-            if (isGuestEphemeral) {
-                setTitle(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_title));
-                setMessage(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_message));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_button), this);
-            } else {
-                setTitle(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_title_non_ephemeral));
-                setMessage(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_message_non_ephemeral));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_NEGATIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_clear_data_button),
-                        this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_save_data_button),
-                        this);
-            }
-            SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
-            setCanceledOnTouchOutside(false);
-            mGuestId = guestId;
-            mTargetId = targetId;
-            mIsGuestEphemeral = isGuestEphemeral;
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
-                    : FalsingManager.HIGH_PENALTY;
-            if (mFalsingManager.isFalseTap(penalty)) {
-                return;
-            }
-            if (mIsGuestEphemeral) {
-                if (which == DialogInterface.BUTTON_POSITIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Ephemeral guest: exit guest, guest is removed by the system
-                    // on exit, since its marked ephemeral
-                    exitGuestUser(mGuestId, mTargetId, false);
-                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                    // Cancel clicked, do nothing
-                    cancel();
-                }
-            } else {
-                if (which == DialogInterface.BUTTON_POSITIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Non-ephemeral guest: exit guest, guest is not removed by the system
-                    // on exit, since its marked non-ephemeral
-                    exitGuestUser(mGuestId, mTargetId, false);
-                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Non-ephemeral guest: remove guest and then exit
-                    exitGuestUser(mGuestId, mTargetId, true);
-                } else if (which == DialogInterface.BUTTON_NEUTRAL) {
-                    // Cancel clicked, do nothing
-                    cancel();
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    final class AddUserDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
-
-        AddUserDialog(Context context) {
-            super(context);
-
-            setTitle(com.android.settingslib.R.string.user_add_user_title);
-            String message = context.getString(
-                                com.android.settingslib.R.string.user_add_user_message_short);
-            UserInfo currentUser = mUserTracker.getUserInfo();
-            if (currentUser != null && currentUser.isGuest() && currentUser.isEphemeral()) {
-                message += context.getString(R.string.user_add_user_message_guest_remove);
-            }
-            setMessage(message);
-            setButton(DialogInterface.BUTTON_NEUTRAL,
-                    context.getString(android.R.string.cancel), this);
-            setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(android.R.string.ok), this);
-            SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
-                    : FalsingManager.MODERATE_PENALTY;
-            if (mFalsingManager.isFalseTap(penalty)) {
-                return;
-            }
-            if (which == BUTTON_NEUTRAL) {
-                cancel();
-            } else {
-                mDialogLaunchAnimator.dismissStack(this);
-                if (ActivityManager.isUserAMonkey()) {
-                    return;
-                }
-                // Use broadcast instead of ShadeController, as this dialog may have started in
-                // another process and normal dagger bindings are not available
-                mBroadcastSender.sendBroadcastAsUser(
-                        new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
-                getContext().startActivityAsUser(
-                        CreateUserActivity.createIntentForStart(getContext()),
-                        mUserTracker.getUserHandle());
-            }
-        }
-    }
-
-    /**
-     * Callback to for when this controller receives the intent to switch users.
-     */
-    public interface UserSwitchCallback {
-        /**
-         * Called when user has switched.
-         */
-        void onUserSwitched();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 1b73539..b1b45b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,6 +58,8 @@
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -196,4 +198,8 @@
     static DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
+
+    /** Binds {@link UserSwitcherController} to its implementation. */
+    @Binds
+    UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
new file mode 100644
index 0000000..9c38dc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.telephony.data.repository
+
+import android.telephony.Annotation
+import android.telephony.TelephonyCallback
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.TelephonyListenerManager
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that encapsulate _some_ telephony-related state. */
+interface TelephonyRepository {
+    /** The state of the current call. */
+    @Annotation.CallState val callState: Flow<Int>
+}
+
+/**
+ * NOTE: This repository tracks only telephony-related state regarding the default mobile
+ * subscription. `TelephonyListenerManager` does not create new instances of `TelephonyManager` on a
+ * per-subscription basis and thus will always be tracking telephony information regarding
+ * `SubscriptionManager.getDefaultSubscriptionId`. See `TelephonyManager` and `SubscriptionManager`
+ * for more documentation.
+ */
+@SysUISingleton
+class TelephonyRepositoryImpl
+@Inject
+constructor(
+    private val manager: TelephonyListenerManager,
+) : TelephonyRepository {
+    @Annotation.CallState
+    override val callState: Flow<Int> = conflatedCallbackFlow {
+        val listener = TelephonyCallback.CallStateListener { state -> trySend(state) }
+
+        manager.addCallStateListener(listener)
+
+        awaitClose { manager.removeCallStateListener(listener) }
+    }
+}
diff --git a/core/java/android/service/cloudsearch/ICloudSearchService.aidl b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
similarity index 72%
rename from core/java/android/service/cloudsearch/ICloudSearchService.aidl
rename to packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
index 104bf99..630fbf2 100644
--- a/core/java/android/service/cloudsearch/ICloudSearchService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
@@ -12,17 +12,15 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */
-
-package android.service.cloudsearch;
-
-import android.app.cloudsearch.SearchRequest;
-
-/**
- * Interface from the system to CloudSearch service.
  *
- * @hide
  */
-oneway interface ICloudSearchService {
-  void onSearch(in SearchRequest request);
+
+package com.android.systemui.telephony.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface TelephonyRepositoryModule {
+    @Binds fun repository(impl: TelephonyRepositoryImpl): TelephonyRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
new file mode 100644
index 0000000..86ca33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.telephony.domain.interactor
+
+import android.telephony.Annotation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.data.repository.TelephonyRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Hosts business logic related to telephony. */
+@SysUISingleton
+class TelephonyInteractor
+@Inject
+constructor(
+    repository: TelephonyRepository,
+) {
+    @Annotation.CallState val callState: Flow<Int> = repository.callState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 5b522dc..0c72b78 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -20,6 +20,7 @@
 
 import com.android.settingslib.users.EditUserInfoController;
 import com.android.systemui.user.data.repository.UserRepositoryModule;
+import com.android.systemui.user.ui.dialog.UserDialogModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -32,6 +33,7 @@
  */
 @Module(
         includes = {
+                UserDialogModule.class,
                 UserRepositoryModule.class,
         }
 )
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 5e2dde6..108ab43 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -53,10 +53,8 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter
 import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
-import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -66,10 +64,10 @@
 
 private const val USER_VIEW = "user_view"
 
-/**
- * Support a fullscreen user switcher
- */
-open class UserSwitcherActivity @Inject constructor(
+/** Support a fullscreen user switcher */
+open class UserSwitcherActivity
+@Inject
+constructor(
     private val userSwitcherController: UserSwitcherController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val falsingCollector: FalsingCollector,
@@ -86,11 +84,12 @@
     private lateinit var addButton: View
     private var addUserRecords = mutableListOf<UserRecord>()
     private val onBackCallback = OnBackInvokedCallback { finish() }
-    private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            finish()
+    private val userSwitchedCallback: UserTracker.Callback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                finish()
+            }
         }
-    }
     // When the add users options become available, insert another option to manage users
     private val manageUserRecord =
         UserRecord(
@@ -114,13 +113,14 @@
     @VisibleForTesting
     fun createActivity() {
         setContentView(R.layout.user_switcher_fullscreen)
-        window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+        window.decorView.systemUiVisibility =
+            (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
         if (isUsingModernArchitecture()) {
             Log.d(TAG, "Using modern architecture.")
-            val viewModel = ViewModelProvider(
-                this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+            val viewModel =
+                ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
             UserSwitcherViewBinder.bind(
                 view = requireViewById(R.id.user_switcher_root),
                 viewModel = viewModel,
@@ -136,27 +136,23 @@
 
         parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
 
-        parent.touchHandler = object : Gefingerpoken {
-            override fun onTouchEvent(ev: MotionEvent?): Boolean {
-                falsingCollector.onTouchEvent(ev)
-                return false
+        parent.touchHandler =
+            object : Gefingerpoken {
+                override fun onTouchEvent(ev: MotionEvent?): Boolean {
+                    falsingCollector.onTouchEvent(ev)
+                    return false
+                }
             }
-        }
 
-        requireViewById<View>(R.id.cancel).apply {
-            setOnClickListener {
-                _ -> finish()
-            }
-        }
+        requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } }
 
-        addButton = requireViewById<View>(R.id.add).apply {
-            setOnClickListener {
-                _ -> showPopupMenu()
-            }
-        }
+        addButton =
+            requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } }
 
         onBackInvokedDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback)
+            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+            onBackCallback
+        )
 
         userSwitcherController.init(parent)
         initBroadcastReceiver()
@@ -169,25 +165,30 @@
         val items = mutableListOf<UserRecord>()
         addUserRecords.forEach { items.add(it) }
 
-        var popupMenuAdapter = ItemAdapter(
-            this,
-            R.layout.user_switcher_fullscreen_popup_item,
-            layoutInflater,
-            { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
-            { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply {
-                setTint(resources.getColor(
-                    R.color.user_switcher_fullscreen_popup_item_tint,
-                    getTheme()
-                ))
-            } }
-        )
+        var popupMenuAdapter =
+            ItemAdapter(
+                this,
+                R.layout.user_switcher_fullscreen_popup_item,
+                layoutInflater,
+                { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
+                { item: UserRecord ->
+                    adapter.findUserIcon(item, true).mutate().apply {
+                        setTint(
+                            resources.getColor(
+                                R.color.user_switcher_fullscreen_popup_item_tint,
+                                getTheme()
+                            )
+                        )
+                    }
+                }
+            )
         popupMenuAdapter.addAll(items)
 
-        popupMenu = UserSwitcherPopupMenu(this).apply {
-            setAnchorView(addButton)
-            setAdapter(popupMenuAdapter)
-            setOnItemClickListener {
-                parent: AdapterView<*>, view: View, pos: Int, id: Long ->
+        popupMenu =
+            UserSwitcherPopupMenu(this).apply {
+                setAnchorView(addButton)
+                setAdapter(popupMenuAdapter)
+                setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long ->
                     if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
                         return@setOnItemClickListener
                     }
@@ -206,10 +207,10 @@
                     if (!item.isAddUser) {
                         this@UserSwitcherActivity.finish()
                     }
-            }
+                }
 
-            show()
-        }
+                show()
+            }
     }
 
     private fun buildUserViews() {
@@ -227,8 +228,8 @@
         val totalWidth = parent.width
         val userViewCount = adapter.getTotalUserViews()
         val maxColumns = getMaxColumns(userViewCount)
-        val horizontalGap = resources
-            .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
+        val horizontalGap =
+            resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
         val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
         val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
 
@@ -299,14 +300,15 @@
     }
 
     private fun initBroadcastReceiver() {
-        broadcastReceiver = object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                val action = intent.getAction()
-                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                    finish()
+        broadcastReceiver =
+            object : BroadcastReceiver() {
+                override fun onReceive(context: Context, intent: Intent) {
+                    val action = intent.getAction()
+                    if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                        finish()
+                    }
                 }
             }
-        }
 
         val filter = IntentFilter()
         filter.addAction(Intent.ACTION_SCREEN_OFF)
@@ -322,9 +324,7 @@
         return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
     }
 
-    /**
-     * Provides views to populate the option menu.
-     */
+    /** Provides views to populate the option menu. */
     private class ItemAdapter(
         val parentContext: Context,
         val resource: Int,
@@ -337,43 +337,27 @@
             val item = getItem(position)
             val view = convertView ?: layoutInflater.inflate(resource, parent, false)
 
-            view.requireViewById<ImageView>(R.id.icon).apply {
-                setImageDrawable(iconGetter(item))
-            }
-            view.requireViewById<TextView>(R.id.text).apply {
-                setText(textGetter(item))
-            }
+            view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) }
+            view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) }
 
             return view
         }
     }
 
-    private inner class UserAdapter : BaseUserAdapter(userSwitcherController) {
+    private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) {
         override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
             val item = getItem(position)
             var view = convertView as ViewGroup?
             if (view == null) {
-                view = layoutInflater.inflate(
-                    R.layout.user_switcher_fullscreen_item,
-                    parent,
-                    false
-                ) as ViewGroup
+                view =
+                    layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false)
+                        as ViewGroup
             }
-            (view.getChildAt(0) as ImageView).apply {
-                setImageDrawable(getDrawable(item))
-            }
-            (view.getChildAt(1) as TextView).apply {
-                setText(getName(getContext(), item))
-            }
+            (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) }
+            (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) }
 
             view.setEnabled(item.isSwitchToEnabled)
-            view.setAlpha(
-                if (view.isEnabled()) {
-                    USER_SWITCH_ENABLED_ALPHA
-                } else {
-                    USER_SWITCH_DISABLED_ALPHA
-                }
-            )
+            UserSwitcherController.setSelectableAlpha(view)
             view.setTag(USER_VIEW)
             return view
         }
@@ -401,23 +385,20 @@
         }
 
         fun getTotalUserViews(): Int {
-            return users.count { item ->
-                !doNotRenderUserView(item)
-            }
+            return users.count { item -> !doNotRenderUserView(item) }
         }
 
         fun doNotRenderUserView(item: UserRecord): Boolean {
-            return item.isAddUser ||
-                    item.isAddSupervisedUser ||
-                    item.isGuest && item.info == null
+            return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null
         }
 
         private fun getDrawable(item: UserRecord): Drawable {
-            var drawable = if (item.isGuest) {
-                getDrawable(R.drawable.ic_account_circle)
-            } else {
-                findUserIcon(item)
-            }
+            var drawable =
+                if (item.isGuest) {
+                    getDrawable(R.drawable.ic_account_circle)
+                } else {
+                    findUserIcon(item)
+                }
             drawable.mutate()
 
             if (!item.isCurrent && !item.isSwitchToEnabled) {
@@ -429,16 +410,16 @@
                 )
             }
 
-            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
-                    as LayerDrawable
-            if (item == userSwitcherController.getCurrentUserRecord()) {
+            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable
+            if (item == userSwitcherController.currentUserRecord) {
                 (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
-                    val stroke = resources
-                        .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
-                    val color = Utils.getColorAttrDefaultColor(
-                        this@UserSwitcherActivity,
-                        com.android.internal.R.attr.colorAccentPrimary
-                    )
+                    val stroke =
+                        resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
+                    val color =
+                        Utils.getColorAttrDefaultColor(
+                            this@UserSwitcherActivity,
+                            com.android.internal.R.attr.colorAccentPrimary
+                        )
 
                     setStroke(stroke, color)
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
similarity index 66%
copy from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
copy to packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
index 9b370d8..4fd55c0 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
@@ -12,17 +12,14 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF 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.cursor;
+package com.android.systemui.user.data.model
 
-/**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
- */
-public final class SoftwareCursorManager {
-
-    public SoftwareCursorManager() {
-      // TODO: Add behavior in a future CL.
-    }
-}
+/** Encapsulates the state of settings related to user switching. */
+data class UserSwitcherSettingsModel(
+    val isSimpleUserSwitcher: Boolean = false,
+    val isAddUsersFromLockscreen: Boolean = false,
+    val isUserSwitcherEnabled: Boolean = false,
+)
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 305b5ee..3014f39 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
@@ -18,9 +18,13 @@
 package com.android.systemui.user.data.repository
 
 import android.content.Context
+import android.content.pm.UserInfo
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
 import androidx.appcompat.content.res.AppCompatResources
 import com.android.internal.util.UserIcons
 import com.android.systemui.R
@@ -29,15 +33,36 @@
 import com.android.systemui.common.shared.model.Text
 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.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Acts as source of truth for user related data.
@@ -55,6 +80,18 @@
     /** List of available user-related actions. */
     val actions: Flow<List<UserActionModel>>
 
+    /** User switcher related settings. */
+    val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
+
+    /** List of all users on the device. */
+    val userInfos: Flow<List<UserInfo>>
+
+    /** [UserInfo] of the currently-selected user. */
+    val selectedUserInfo: Flow<UserInfo>
+
+    /** User ID of the last non-guest selected user. */
+    val lastSelectedNonGuestUserId: Int
+
     /** Whether actions are available even when locked. */
     val isActionableWhenLocked: Flow<Boolean>
 
@@ -62,7 +99,23 @@
     val isGuestUserAutoCreated: Boolean
 
     /** Whether the guest user is currently being reset. */
-    val isGuestUserResetting: Boolean
+    var isGuestUserResetting: Boolean
+
+    /** Whether we've scheduled the creation of a guest user. */
+    val isGuestUserCreationScheduled: AtomicBoolean
+
+    /** The user of the secondary service. */
+    var secondaryUserId: Int
+
+    /** Whether refresh users should be paused. */
+    var isRefreshUsersPaused: Boolean
+
+    /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
+    fun refreshUsers()
+
+    fun getSelectedUserInfo(): UserInfo
+
+    fun isSimpleUserSwitcher(): Boolean
 }
 
 @SysUISingleton
@@ -71,9 +124,31 @@
 constructor(
     @Application private val appContext: Context,
     private val manager: UserManager,
-    controller: UserSwitcherController,
+    private val controller: UserSwitcherController,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val globalSettings: GlobalSettings,
+    private val tracker: UserTracker,
+    private val featureFlags: FeatureFlags,
 ) : UserRepository {
 
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+    private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null)
+    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+        _userSwitcherSettings.asStateFlow().filterNotNull()
+
+    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 lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+        private set
+
     private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
         fun send() {
             trySendWithFailureLogging(
@@ -99,11 +174,148 @@
     override val actions: Flow<List<UserActionModel>> =
         userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
 
-    override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen
+    override val isActionableWhenLocked: Flow<Boolean> =
+        if (isNewImpl) {
+            emptyFlow()
+        } else {
+            controller.isAddUsersFromLockScreenEnabled
+        }
 
-    override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated
+    override val isGuestUserAutoCreated: Boolean =
+        if (isNewImpl) {
+            appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
+        } else {
+            controller.isGuestUserAutoCreated
+        }
 
-    override val isGuestUserResetting: Boolean = controller.isGuestUserResetting
+    private var _isGuestUserResetting: Boolean = false
+    override var isGuestUserResetting: Boolean =
+        if (isNewImpl) {
+            _isGuestUserResetting
+        } else {
+            controller.isGuestUserResetting
+        }
+        set(value) =
+            if (isNewImpl) {
+                _isGuestUserResetting = value
+            } else {
+                error("Not supported in the old implementation!")
+            }
+
+    override val isGuestUserCreationScheduled = AtomicBoolean()
+
+    override var secondaryUserId: Int = UserHandle.USER_NULL
+
+    override var isRefreshUsersPaused: Boolean = false
+
+    init {
+        if (isNewImpl) {
+            observeSelectedUser()
+            observeUserSettings()
+        }
+    }
+
+    override fun refreshUsers() {
+        applicationScope.launch {
+            val result = withContext(backgroundDispatcher) { manager.aliveUsers }
+
+            if (result != null) {
+                _userInfos.value = result
+            }
+        }
+    }
+
+    override fun getSelectedUserInfo(): UserInfo {
+        return checkNotNull(_selectedUserInfo.value)
+    }
+
+    override fun isSimpleUserSwitcher(): Boolean {
+        return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+    }
+
+    private fun observeSelectedUser() {
+        conflatedCallbackFlow {
+                fun send() {
+                    trySendWithFailureLogging(tracker.userInfo, TAG)
+                }
+
+                val callback =
+                    object : UserTracker.Callback {
+                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                            send()
+                        }
+                    }
+
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                send()
+
+                awaitClose { tracker.removeCallback(callback) }
+            }
+            .onEach {
+                if (!it.isGuest) {
+                    lastSelectedNonGuestUserId = it.id
+                }
+
+                _selectedUserInfo.value = it
+            }
+            .launchIn(applicationScope)
+    }
+
+    private fun observeUserSettings() {
+        globalSettings
+            .observerFlow(
+                names =
+                    arrayOf(
+                        SETTING_SIMPLE_USER_SWITCHER,
+                        Settings.Global.ADD_USERS_WHEN_LOCKED,
+                        Settings.Global.USER_SWITCHER_ENABLED,
+                    ),
+                userId = UserHandle.USER_SYSTEM,
+            )
+            .onStart { emit(Unit) } // Forces an initial update.
+            .map { getSettings() }
+            .onEach { _userSwitcherSettings.value = it }
+            .launchIn(applicationScope)
+    }
+
+    private suspend fun getSettings(): UserSwitcherSettingsModel {
+        return withContext(backgroundDispatcher) {
+            val isSimpleUserSwitcher =
+                globalSettings.getIntForUser(
+                    SETTING_SIMPLE_USER_SWITCHER,
+                    if (
+                        appContext.resources.getBoolean(
+                            com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+                        )
+                    ) {
+                        1
+                    } else {
+                        0
+                    },
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            val isAddUsersFromLockscreen =
+                globalSettings.getIntForUser(
+                    Settings.Global.ADD_USERS_WHEN_LOCKED,
+                    0,
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            val isUserSwitcherEnabled =
+                globalSettings.getIntForUser(
+                    Settings.Global.USER_SWITCHER_ENABLED,
+                    0,
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            UserSwitcherSettingsModel(
+                isSimpleUserSwitcher = isSimpleUserSwitcher,
+                isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+                isUserSwitcherEnabled = isUserSwitcherEnabled,
+            )
+        }
+    }
 
     private fun UserRecord.isUser(): Boolean {
         return when {
@@ -125,6 +337,7 @@
             image = getUserImage(this),
             isSelected = isCurrent,
             isSelectable = isSwitchToEnabled || isGuest,
+            isGuest = isGuest,
         )
     }
 
@@ -162,5 +375,6 @@
 
     companion object {
         private const val TAG = "UserRepository"
+        @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index cf6da9a..9370286 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -19,6 +19,7 @@
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.os.UserHandle
+import com.android.settingslib.RestrictedLockUtils
 
 /** Encapsulates raw data for a user or an option item related to managing users on the device. */
 data class UserRecord(
@@ -41,6 +42,11 @@
     @JvmField val isSwitchToEnabled: Boolean = false,
     /** Whether this record represents an option to add another supervised user to the device. */
     @JvmField val isAddSupervisedUser: Boolean = false,
+    /**
+     * An enforcing admin, if the user action represented by this record is disabled by the admin.
+     * If not disabled, this is `null`.
+     */
+    @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
 ) {
     /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
     fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
@@ -59,6 +65,14 @@
         }
     }
 
+    /**
+     * Returns `true` if the user action represented by this record has been disabled by an admin;
+     * `false` otherwise.
+     */
+    fun isDisabledByAdmin(): Boolean {
+        return enforcedAdmin != null
+    }
+
     companion object {
         @JvmStatic
         fun createForGuest(): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
new file mode 100644
index 0000000..07e5cf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.annotation.UserIdInt
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import android.view.WindowManagerGlobal
+import android.widget.Toast
+import com.android.internal.logging.UiEventLogger
+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.qs.QSUserSwitcherEvent
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+/** Encapsulates business logic to interact with guest user data and systems. */
+@SysUISingleton
+class GuestUserInteractor
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val manager: UserManager,
+    private val repository: UserRepository,
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val refreshUsersScheduler: RefreshUsersScheduler,
+    private val uiEventLogger: UiEventLogger,
+) {
+    /** Whether the device is configured to always have a guest user available. */
+    val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+
+    /** Whether the guest user is currently being reset. */
+    val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+
+    /** Notifies that the device has finished booting. */
+    fun onDeviceBootCompleted() {
+        applicationScope.launch {
+            if (isDeviceAllowedToAddGuest()) {
+                guaranteePresent()
+                return@launch
+            }
+
+            suspendCancellableCoroutine<Unit> { continuation ->
+                val callback =
+                    object : DeviceProvisionedController.DeviceProvisionedListener {
+                        override fun onDeviceProvisionedChanged() {
+                            continuation.resumeWith(Result.success(Unit))
+                            deviceProvisionedController.removeCallback(this)
+                        }
+                    }
+
+                deviceProvisionedController.addCallback(callback)
+            }
+
+            if (isDeviceAllowedToAddGuest()) {
+                guaranteePresent()
+            }
+        }
+    }
+
+    /** Creates a guest user and switches to it. */
+    fun createAndSwitchTo(
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        selectUser: (userId: Int) -> Unit,
+    ) {
+        applicationScope.launch {
+            val newGuestUserId = create(showDialog, dismissDialog)
+            if (newGuestUserId != UserHandle.USER_NULL) {
+                selectUser(newGuestUserId)
+            }
+        }
+    }
+
+    /** Exits the guest user, switching back to the last non-guest user or to the default user. */
+    fun exit(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean,
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        switchUser: (userId: Int) -> Unit,
+    ) {
+        val currentUserInfo = repository.getSelectedUserInfo()
+        if (currentUserInfo.id != guestUserId) {
+            Log.w(
+                TAG,
+                "User requesting to start a new session ($guestUserId) is not current user" +
+                    " (${currentUserInfo.id})"
+            )
+            return
+        }
+
+        if (!currentUserInfo.isGuest) {
+            Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+            return
+        }
+
+        applicationScope.launch {
+            var newUserId = UserHandle.USER_SYSTEM
+            if (targetUserId == UserHandle.USER_NULL) {
+                // When a target user is not specified switch to last non guest user:
+                val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
+                if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
+                    val info =
+                        withContext(backgroundDispatcher) {
+                            manager.getUserInfo(lastSelectedNonGuestUserHandle)
+                        }
+                    if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
+                        newUserId = info.id
+                    }
+                }
+            } else {
+                newUserId = targetUserId
+            }
+
+            if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
+                remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
+            } else {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
+                switchUser(newUserId)
+            }
+        }
+    }
+
+    /**
+     * Guarantees that the guest user is present on the device, creating it if needed and if allowed
+     * to.
+     */
+    suspend fun guaranteePresent() {
+        if (!isDeviceAllowedToAddGuest()) {
+            return
+        }
+
+        val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
+        if (guestUser == null) {
+            scheduleCreation()
+        }
+    }
+
+    /** Removes the guest user from the device. */
+    suspend fun remove(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        switchUser: (userId: Int) -> Unit,
+    ) {
+        val currentUser: UserInfo = repository.getSelectedUserInfo()
+        if (currentUser.id != guestUserId) {
+            Log.w(
+                TAG,
+                "User requesting to start a new session ($guestUserId) is not current user" +
+                    " ($currentUser.id)"
+            )
+            return
+        }
+
+        if (!currentUser.isGuest) {
+            Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+            return
+        }
+
+        val marked =
+            withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
+        if (!marked) {
+            Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
+            return
+        }
+
+        if (targetUserId == UserHandle.USER_NULL) {
+            // Create a new guest in the foreground, and then immediately switch to it
+            val newGuestId = create(showDialog, dismissDialog)
+            if (newGuestId == UserHandle.USER_NULL) {
+                Log.e(TAG, "Could not create new guest, switching back to system user")
+                switchUser(UserHandle.USER_SYSTEM)
+                withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+                try {
+                    WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+                } catch (e: RemoteException) {
+                    Log.e(
+                        TAG,
+                        "Couldn't remove guest because ActivityManager or WindowManager is dead"
+                    )
+                }
+                return
+            }
+
+            switchUser(newGuestId)
+
+            withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+        } else {
+            if (repository.isGuestUserAutoCreated) {
+                repository.isGuestUserResetting = true
+            }
+            switchUser(targetUserId)
+            manager.removeUser(currentUser.id)
+        }
+    }
+
+    /**
+     * Creates the guest user and adds it to the device.
+     *
+     * @param showDialog A function to invoke to show a dialog.
+     * @param dismissDialog A function to invoke to dismiss a dialog.
+     * @return The user ID of the newly-created guest user.
+     */
+    private suspend fun create(
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+    ): Int {
+        return withContext(mainDispatcher) {
+            showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            val guestUserId = createInBackground()
+            dismissDialog()
+            if (guestUserId != UserHandle.USER_NULL) {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
+            } else {
+                Toast.makeText(
+                        applicationContext,
+                        com.android.settingslib.R.string.add_guest_failed,
+                        Toast.LENGTH_SHORT,
+                    )
+                    .show()
+            }
+
+            guestUserId
+        }
+    }
+
+    /** Schedules the creation of the guest user. */
+    private suspend fun scheduleCreation() {
+        if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
+            return
+        }
+
+        withContext(backgroundDispatcher) {
+            val newGuestUserId = createInBackground()
+            repository.isGuestUserCreationScheduled.set(false)
+            repository.isGuestUserResetting = false
+            if (newGuestUserId == UserHandle.USER_NULL) {
+                Log.w(TAG, "Could not create new guest while exiting existing guest")
+                // Refresh users so that we still display "Guest" if
+                // config_guestUserAutoCreated=true
+                refreshUsersScheduler.refreshIfNotPaused()
+            }
+        }
+    }
+
+    /**
+     * Creates a guest user and return its multi-user user ID.
+     *
+     * This method does not check if a guest already exists before it makes a call to [UserManager]
+     * to create a new one.
+     *
+     * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
+     * the guest couldn't be created.
+     */
+    @UserIdInt
+    private suspend fun createInBackground(): Int {
+        return withContext(backgroundDispatcher) {
+            try {
+                val guestUser = manager.createGuest(applicationContext)
+                if (guestUser != null) {
+                    guestUser.id
+                } else {
+                    Log.e(
+                        TAG,
+                        "Couldn't create guest, most likely because there already exists one!"
+                    )
+                    UserHandle.USER_NULL
+                }
+            } catch (e: UserManager.UserOperationException) {
+                Log.e(TAG, "Couldn't create guest user!", e)
+                UserHandle.USER_NULL
+            }
+        }
+    }
+
+    private fun isDeviceAllowedToAddGuest(): Boolean {
+        return deviceProvisionedController.isDeviceProvisioned &&
+            !devicePolicyManager.isDeviceManaged
+    }
+
+    companion object {
+        private const val TAG = "GuestUserInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
new file mode 100644
index 0000000..8f36821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
@@ -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.systemui.user.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Encapsulates logic for pausing, unpausing, and scheduling a delayed job. */
+@SysUISingleton
+class RefreshUsersScheduler
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val repository: UserRepository,
+) {
+    private var scheduledUnpauseJob: Job? = null
+    private var isPaused = false
+
+    fun pause() {
+        applicationScope.launch(mainDispatcher) {
+            isPaused = true
+            scheduledUnpauseJob?.cancel()
+            scheduledUnpauseJob =
+                applicationScope.launch {
+                    delay(PAUSE_REFRESH_USERS_TIMEOUT_MS)
+                    unpauseAndRefresh()
+                }
+        }
+    }
+
+    fun unpauseAndRefresh() {
+        applicationScope.launch(mainDispatcher) {
+            isPaused = false
+            refreshIfNotPaused()
+        }
+    }
+
+    fun refreshIfNotPaused() {
+        applicationScope.launch(mainDispatcher) {
+            if (isPaused) {
+                return@launch
+            }
+
+            repository.refreshUsers()
+        }
+    }
+
+    companion object {
+        private const val PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
new file mode 100644
index 0000000..1b4746a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.systemui.user.data.repository.UserRepository
+
+/** Utilities related to user management actions. */
+object UserActionsUtil {
+
+    /** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
+    fun canCreateGuest(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        if (!isUserSwitcherEnabled) {
+            return false
+        }
+
+        return currentUserCanCreateUsers(manager, repository) ||
+            anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+    }
+
+    /** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
+    fun canCreateUser(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        if (!isUserSwitcherEnabled) {
+            return false
+        }
+
+        if (
+            !currentUserCanCreateUsers(manager, repository) &&
+                !anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+        ) {
+            return false
+        }
+
+        return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
+    }
+
+    /**
+     * Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
+     */
+    fun canCreateSupervisedUser(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+        supervisedUserPackageName: String?
+    ): Boolean {
+        if (supervisedUserPackageName.isNullOrEmpty()) {
+            return false
+        }
+
+        return canCreateUser(
+            manager,
+            repository,
+            isUserSwitcherEnabled,
+            isAddUsersFromLockScreenEnabled
+        )
+    }
+
+    /**
+     * Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
+     */
+    private fun currentUserCanCreateUsers(
+        manager: UserManager,
+        repository: UserRepository,
+    ): Boolean {
+        val currentUser = repository.getSelectedUserInfo()
+        if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
+            return false
+        }
+
+        return systemCanCreateUsers(manager)
+    }
+
+    /** Returns `true` if the system can add users to the device; `false` otherwise. */
+    private fun systemCanCreateUsers(
+        manager: UserManager,
+    ): Boolean {
+        return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
+    }
+
+    /** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
+    private fun anyoneCanCreateUsers(
+        manager: UserManager,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
+    }
+}
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 3c5b969..a84238c 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
@@ -17,94 +17,725 @@
 
 package com.android.systemui.user.domain.interactor
 
+import android.annotation.SuppressLint
+import android.annotation.UserIdInt
+import android.app.ActivityManager
+import android.content.Context
 import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
 import android.provider.Settings
+import android.util.Log
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
+import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.Text
 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.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+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.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
 
 /** Encapsulates business logic to interact with user data and systems. */
 @SysUISingleton
 class UserInteractor
 @Inject
 constructor(
-    repository: UserRepository,
+    @Application private val applicationContext: Context,
+    private val repository: UserRepository,
     private val controller: UserSwitcherController,
     private val activityStarter: ActivityStarter,
-    keyguardInteractor: KeyguardInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val featureFlags: FeatureFlags,
+    private val manager: UserManager,
+    @Application private val applicationScope: CoroutineScope,
+    telephonyInteractor: TelephonyInteractor,
+    broadcastDispatcher: BroadcastDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val activityManager: ActivityManager,
+    private val refreshUsersScheduler: RefreshUsersScheduler,
+    private val guestUserInteractor: GuestUserInteractor,
 ) {
+    /**
+     * Defines interface for classes that can be notified when the state of users on the device is
+     * changed.
+     */
+    interface UserCallback {
+        /** Returns `true` if this callback can be cleaned-up. */
+        fun isEvictable(): Boolean = false
+        /** Notifies that the state of users on the device has changed. */
+        fun onUserStateChanged()
+    }
+
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+    private val supervisedUserPackageName: String?
+        get() =
+            applicationContext.getString(
+                com.android.internal.R.string.config_supervisedUserCreationPackage
+            )
+
+    private val callbackMutex = Mutex()
+    private val callbacks = mutableSetOf<UserCallback>()
+
     /** List of current on-device users to select from. */
-    val users: Flow<List<UserModel>> = repository.users
+    val users: Flow<List<UserModel>>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.userInfos,
+                    repository.selectedUserInfo,
+                    repository.userSwitcherSettings,
+                ) { userInfos, selectedUserInfo, settings ->
+                    toUserModels(
+                        userInfos = userInfos,
+                        selectedUserId = selectedUserInfo.id,
+                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                    )
+                }
+            } else {
+                repository.users
+            }
 
     /** The currently-selected user. */
-    val selectedUser: Flow<UserModel> = repository.selectedUser
+    val selectedUser: Flow<UserModel>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.selectedUserInfo,
+                    repository.userSwitcherSettings,
+                ) { selectedUserInfo, settings ->
+                    val selectedUserId = selectedUserInfo.id
+                    checkNotNull(
+                        toUserModel(
+                            userInfo = selectedUserInfo,
+                            selectedUserId = selectedUserId,
+                            canSwitchUsers = canSwitchUsers(selectedUserId),
+                            isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                        )
+                    )
+                }
+            } else {
+                repository.selectedUser
+            }
 
     /** List of user-switcher related actions that are available. */
-    val actions: Flow<List<UserActionModel>> =
-        combine(
-                repository.isActionableWhenLocked,
-                keyguardInteractor.isKeyguardShowing,
-            ) { isActionableWhenLocked, isLocked ->
-                isActionableWhenLocked || !isLocked
-            }
-            .flatMapLatest { isActionable ->
-                if (isActionable) {
-                    repository.actions.map { actions ->
-                        actions +
-                            if (actions.isNotEmpty()) {
-                                // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because
-                                // that's a user
-                                // switcher specific action that is not known to the our data source
-                                // or other
-                                // features.
-                                listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-                            } else {
-                                // If no actions, don't add the navigate action.
-                                emptyList()
-                            }
+    val actions: Flow<List<UserActionModel>>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.userInfos,
+                    repository.userSwitcherSettings,
+                    keyguardInteractor.isKeyguardShowing,
+                ) { userInfos, settings, isDeviceLocked ->
+                    buildList {
+                        val hasGuestUser = userInfos.any { it.isGuest }
+                        if (
+                            !hasGuestUser &&
+                                (guestUserInteractor.isGuestUserAutoCreated ||
+                                    UserActionsUtil.canCreateGuest(
+                                        manager,
+                                        repository,
+                                        settings.isUserSwitcherEnabled,
+                                        settings.isAddUsersFromLockscreen,
+                                    ))
+                        ) {
+                            add(UserActionModel.ENTER_GUEST_MODE)
+                        }
+
+                        if (isDeviceLocked && !settings.isAddUsersFromLockscreen) {
+                            // The device is locked and our setting to allow actions that add users
+                            // from the lock-screen is not enabled. The guest action from above is
+                            // always allowed, even when the device is locked, but the various "add
+                            // user" actions below are not. We can finish building the list here.
+                            return@buildList
+                        }
+
+                        if (
+                            UserActionsUtil.canCreateUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_USER)
+                        }
+
+                        if (
+                            UserActionsUtil.canCreateSupervisedUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                                supervisedUserPackageName,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_SUPERVISED_USER)
+                        }
                     }
-                } else {
-                    // If not actionable it means that we're not allowed to show actions when locked
-                    // and we
-                    // are locked. Therefore, we should show no actions.
-                    flowOf(emptyList())
                 }
+            } else {
+                combine(
+                        repository.isActionableWhenLocked,
+                        keyguardInteractor.isKeyguardShowing,
+                    ) { isActionableWhenLocked, isLocked ->
+                        isActionableWhenLocked || !isLocked
+                    }
+                    .flatMapLatest { isActionable ->
+                        if (isActionable) {
+                            repository.actions.map { actions ->
+                                actions +
+                                    if (actions.isNotEmpty()) {
+                                        // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
+                                        // because that's a user switcher specific action that is
+                                        // not known to the our data source or other features.
+                                        listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+                                    } else {
+                                        // If no actions, don't add the navigate action.
+                                        emptyList()
+                                    }
+                            }
+                        } else {
+                            // If not actionable it means that we're not allowed to show actions
+                            // when
+                            // locked and we are locked. Therefore, we should show no actions.
+                            flowOf(emptyList())
+                        }
+                    }
             }
 
+    val userRecords: StateFlow<ArrayList<UserRecord>> =
+        if (isNewImpl) {
+            combine(
+                    repository.userInfos,
+                    repository.selectedUserInfo,
+                    actions,
+                    repository.userSwitcherSettings,
+                ) { userInfos, selectedUserInfo, actionModels, settings ->
+                    ArrayList(
+                        userInfos.map {
+                            toRecord(
+                                userInfo = it,
+                                selectedUserId = selectedUserInfo.id,
+                            )
+                        } +
+                            actionModels.map {
+                                toRecord(
+                                    action = it,
+                                    selectedUserId = selectedUserInfo.id,
+                                    isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+                                )
+                            }
+                    )
+                }
+                .onEach { notifyCallbacks() }
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = ArrayList(),
+                )
+        } else {
+            MutableStateFlow(ArrayList())
+        }
+
+    val selectedUserRecord: StateFlow<UserRecord?> =
+        if (isNewImpl) {
+            repository.selectedUserInfo
+                .map { selectedUserInfo ->
+                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+                }
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = null,
+                )
+        } else {
+            MutableStateFlow(null)
+        }
+
     /** Whether the device is configured to always have a guest user available. */
-    val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+    val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
-    val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+    val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+
+    private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
+    val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
+
+    private val _dialogDismissRequests = MutableStateFlow<Unit?>(null)
+    val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
+
+    val isSimpleUserSwitcher: Boolean
+        get() =
+            if (isNewImpl) {
+                repository.isSimpleUserSwitcher()
+            } else {
+                error("Not supported in the old implementation!")
+            }
+
+    init {
+        if (isNewImpl) {
+            refreshUsersScheduler.refreshIfNotPaused()
+            telephonyInteractor.callState
+                .distinctUntilChanged()
+                .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+                .launchIn(applicationScope)
+
+            combine(
+                    broadcastDispatcher.broadcastFlow(
+                        filter =
+                            IntentFilter().apply {
+                                addAction(Intent.ACTION_USER_ADDED)
+                                addAction(Intent.ACTION_USER_REMOVED)
+                                addAction(Intent.ACTION_USER_INFO_CHANGED)
+                                addAction(Intent.ACTION_USER_SWITCHED)
+                                addAction(Intent.ACTION_USER_STOPPED)
+                                addAction(Intent.ACTION_USER_UNLOCKED)
+                            },
+                        user = UserHandle.SYSTEM,
+                        map = { intent, _ -> intent },
+                    ),
+                    repository.selectedUserInfo.pairwise(null),
+                ) { intent, selectedUserChange ->
+                    Pair(intent, selectedUserChange.previousValue)
+                }
+                .onEach { (intent, previousSelectedUser) ->
+                    onBroadcastReceived(intent, previousSelectedUser)
+                }
+                .launchIn(applicationScope)
+        }
+    }
+
+    fun addCallback(callback: UserCallback) {
+        applicationScope.launch { callbackMutex.withLock { callbacks.add(callback) } }
+    }
+
+    fun removeCallback(callback: UserCallback) {
+        applicationScope.launch { callbackMutex.withLock { callbacks.remove(callback) } }
+    }
+
+    fun refreshUsers() {
+        refreshUsersScheduler.refreshIfNotPaused()
+    }
+
+    fun onDialogShown() {
+        _dialogShowRequests.value = null
+    }
+
+    fun onDialogDismissed() {
+        _dialogDismissRequests.value = null
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("UserInteractor state:")
+        pw.println("  lastSelectedNonGuestUserId=${repository.lastSelectedNonGuestUserId}")
+
+        val users = userRecords.value.filter { it.info != null }
+        pw.println("  userCount=${userRecords.value.count { LegacyUserDataHelper.isUser(it) }}")
+        for (i in users.indices) {
+            pw.println("    ${users[i]}")
+        }
+
+        val actions = userRecords.value.filter { it.info == null }
+        pw.println("  actionCount=${userRecords.value.count { !LegacyUserDataHelper.isUser(it) }}")
+        for (i in actions.indices) {
+            pw.println("    ${actions[i]}")
+        }
+
+        pw.println("isSimpleUserSwitcher=$isSimpleUserSwitcher")
+        pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
+    }
+
+    fun onDeviceBootCompleted() {
+        guestUserInteractor.onDeviceBootCompleted()
+    }
 
     /** Switches to the user with the given user ID. */
     fun selectUser(
-        userId: Int,
+        newlySelectedUserId: Int,
     ) {
-        controller.onUserSelected(userId, /* dialogShower= */ null)
+        if (isNewImpl) {
+            val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+            if (
+                newlySelectedUserId == currentlySelectedUserInfo.id &&
+                    currentlySelectedUserInfo.isGuest
+            ) {
+                // Here when clicking on the currently-selected guest user to leave guest mode
+                // and return to the previously-selected non-guest user.
+                showDialog(
+                    ShowDialogRequestModel.ShowExitGuestDialog(
+                        guestUserId = currentlySelectedUserInfo.id,
+                        targetUserId = repository.lastSelectedNonGuestUserId,
+                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        onExitGuestUser = this::exitGuestUser,
+                    )
+                )
+                return
+            }
+
+            if (currentlySelectedUserInfo.isGuest) {
+                // Here when switching from guest to a non-guest user.
+                showDialog(
+                    ShowDialogRequestModel.ShowExitGuestDialog(
+                        guestUserId = currentlySelectedUserInfo.id,
+                        targetUserId = newlySelectedUserId,
+                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        onExitGuestUser = this::exitGuestUser,
+                    )
+                )
+                return
+            }
+
+            switchUser(newlySelectedUserId)
+        } else {
+            controller.onUserSelected(newlySelectedUserId, /* dialogShower= */ null)
+        }
     }
 
     /** Executes the given action. */
     fun executeAction(action: UserActionModel) {
-        when (action) {
-            UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
-            UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
-            UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
-            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                activityStarter.startActivity(
-                    Intent(Settings.ACTION_USER_SETTINGS),
-                    /* dismissShade= */ false,
-                )
+        if (isNewImpl) {
+            when (action) {
+                UserActionModel.ENTER_GUEST_MODE ->
+                    guestUserInteractor.createAndSwitchTo(
+                        this::showDialog,
+                        this::dismissDialog,
+                        this::selectUser,
+                    )
+                UserActionModel.ADD_USER -> {
+                    val currentUser = repository.getSelectedUserInfo()
+                    showDialog(
+                        ShowDialogRequestModel.ShowAddUserDialog(
+                            userHandle = currentUser.userHandle,
+                            isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                            showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                        )
+                    )
+                }
+                UserActionModel.ADD_SUPERVISED_USER ->
+                    activityStarter.startActivity(
+                        Intent()
+                            .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                            .setPackage(supervisedUserPackageName)
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                        /* dismissShade= */ false,
+                    )
+                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                    activityStarter.startActivity(
+                        Intent(Settings.ACTION_USER_SETTINGS),
+                        /* dismissShade= */ false,
+                    )
+            }
+        } else {
+            when (action) {
+                UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
+                UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
+                UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
+                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                    activityStarter.startActivity(
+                        Intent(Settings.ACTION_USER_SETTINGS),
+                        /* dismissShade= */ false,
+                    )
+            }
         }
     }
+
+    fun exitGuestUser(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean,
+    ) {
+        guestUserInteractor.exit(
+            guestUserId = guestUserId,
+            targetUserId = targetUserId,
+            forceRemoveGuestOnExit = forceRemoveGuestOnExit,
+            showDialog = this::showDialog,
+            dismissDialog = this::dismissDialog,
+            switchUser = this::switchUser,
+        )
+    }
+
+    fun removeGuestUser(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+    ) {
+        applicationScope.launch {
+            guestUserInteractor.remove(
+                guestUserId = guestUserId,
+                targetUserId = targetUserId,
+                ::showDialog,
+                ::dismissDialog,
+                ::selectUser,
+            )
+        }
+    }
+
+    private fun showDialog(request: ShowDialogRequestModel) {
+        _dialogShowRequests.value = request
+    }
+
+    private fun dismissDialog() {
+        _dialogDismissRequests.value = Unit
+    }
+
+    private fun notifyCallbacks() {
+        applicationScope.launch {
+            callbackMutex.withLock {
+                val iterator = callbacks.iterator()
+                while (iterator.hasNext()) {
+                    val callback = iterator.next()
+                    if (!callback.isEvictable()) {
+                        callback.onUserStateChanged()
+                    } else {
+                        iterator.remove()
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun toRecord(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+    ): UserRecord {
+        return LegacyUserDataHelper.createRecord(
+            context = applicationContext,
+            manager = manager,
+            userInfo = userInfo,
+            picture = null,
+            isCurrent = userInfo.id == selectedUserId,
+            canSwitchUsers = canSwitchUsers(selectedUserId),
+        )
+    }
+
+    private suspend fun toRecord(
+        action: UserActionModel,
+        selectedUserId: Int,
+        isAddFromLockscreenEnabled: Boolean,
+    ): UserRecord {
+        return LegacyUserDataHelper.createRecord(
+            context = applicationContext,
+            selectedUserId = selectedUserId,
+            actionType = action,
+            isRestricted =
+                if (action == UserActionModel.ENTER_GUEST_MODE) {
+                    // Entering guest mode is never restricted, so it's allowed to happen from the
+                    // lockscreen even if the "add from lockscreen" system setting is off.
+                    false
+                } else {
+                    !isAddFromLockscreenEnabled
+                },
+            isSwitchToEnabled =
+                canSwitchUsers(selectedUserId) &&
+                    // If the user is auto-created is must not be currently resetting.
+                    !(isGuestUserAutoCreated && isGuestUserResetting),
+        )
+    }
+
+    private fun switchUser(userId: Int) {
+        // TODO(b/246631653): track jank and lantecy like in the old impl.
+        refreshUsersScheduler.pause()
+        try {
+            activityManager.switchUser(userId)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Couldn't switch user.", e)
+        }
+    }
+
+    private suspend fun onBroadcastReceived(
+        intent: Intent,
+        previousUserInfo: UserInfo?,
+    ) {
+        val shouldRefreshAllUsers =
+            when (intent.action) {
+                Intent.ACTION_USER_SWITCHED -> {
+                    dismissDialog()
+                    val selectedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+                    if (previousUserInfo?.id != selectedUserId) {
+                        notifyCallbacks()
+                        restartSecondaryService(selectedUserId)
+                    }
+                    if (guestUserInteractor.isGuestUserAutoCreated) {
+                        guestUserInteractor.guaranteePresent()
+                    }
+                    true
+                }
+                Intent.ACTION_USER_INFO_CHANGED -> true
+                Intent.ACTION_USER_UNLOCKED -> {
+                    // If we unlocked the system user, we should refresh all users.
+                    intent.getIntExtra(
+                        Intent.EXTRA_USER_HANDLE,
+                        UserHandle.USER_NULL,
+                    ) == UserHandle.USER_SYSTEM
+                }
+                else -> true
+            }
+
+        if (shouldRefreshAllUsers) {
+            refreshUsersScheduler.unpauseAndRefresh()
+        }
+    }
+
+    private fun restartSecondaryService(@UserIdInt userId: Int) {
+        val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java)
+        // Disconnect from the old secondary user's service
+        val secondaryUserId = repository.secondaryUserId
+        if (secondaryUserId != UserHandle.USER_NULL) {
+            applicationContext.stopServiceAsUser(
+                intent,
+                UserHandle.of(secondaryUserId),
+            )
+            repository.secondaryUserId = UserHandle.USER_NULL
+        }
+
+        // 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) {
+            applicationContext.startServiceAsUser(
+                intent,
+                UserHandle.of(userId),
+            )
+            repository.secondaryUserId = userId
+        }
+    }
+
+    private suspend fun toUserModels(
+        userInfos: List<UserInfo>,
+        selectedUserId: Int,
+        isUserSwitcherEnabled: Boolean,
+    ): List<UserModel> {
+        val canSwitchUsers = canSwitchUsers(selectedUserId)
+
+        return userInfos
+            // The guest user should go in the last position.
+            .sortedBy { it.isGuest }
+            .mapNotNull { userInfo ->
+                toUserModel(
+                    userInfo = userInfo,
+                    selectedUserId = selectedUserId,
+                    canSwitchUsers = canSwitchUsers,
+                    isUserSwitcherEnabled = isUserSwitcherEnabled,
+                )
+            }
+    }
+
+    private suspend fun toUserModel(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+        canSwitchUsers: Boolean,
+        isUserSwitcherEnabled: Boolean,
+    ): UserModel? {
+        val userId = userInfo.id
+        val isSelected = userId == selectedUserId
+
+        return when {
+            // When the user switcher is not enabled in settings, we only show the primary user.
+            !isUserSwitcherEnabled && !userInfo.isPrimary -> null
+
+            // We avoid showing disabled users.
+            !userInfo.isEnabled -> null
+            userInfo.isGuest ->
+                UserModel(
+                    id = userId,
+                    name = Text.Loaded(userInfo.name),
+                    image =
+                        getUserImage(
+                            isGuest = true,
+                            userId = userId,
+                        ),
+                    isSelected = isSelected,
+                    isSelectable = canSwitchUsers,
+                    isGuest = true,
+                )
+            userInfo.supportsSwitchToByUser() ->
+                UserModel(
+                    id = userId,
+                    name = Text.Loaded(userInfo.name),
+                    image =
+                        getUserImage(
+                            isGuest = false,
+                            userId = userId,
+                        ),
+                    isSelected = isSelected,
+                    isSelectable = canSwitchUsers || isSelected,
+                    isGuest = false,
+                )
+            else -> null
+        }
+    }
+
+    private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
+        return withContext(backgroundDispatcher) {
+            manager.getUserSwitchability(UserHandle.of(selectedUserId))
+        } == UserManager.SWITCHABILITY_STATUS_OK
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private suspend fun getUserImage(
+        isGuest: Boolean,
+        userId: Int,
+    ): Drawable {
+        if (isGuest) {
+            return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle))
+        }
+
+        // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
+        // TODO(b/246631653): downscale the bitmaps to R.dimen.max_avatar_size if requested.
+        val userIcon = withContext(backgroundDispatcher) { manager.getUserIcon(userId) }
+        if (userIcon != null) {
+            return BitmapDrawable(userIcon)
+        }
+
+        return UserIcons.getDefaultUserIcon(
+            applicationContext.resources,
+            userId,
+            /* light= */ false
+        )
+    }
+
+    companion object {
+        private const val TAG = "UserInteractor"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
new file mode 100644
index 0000000..08d7c5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.model
+
+import android.os.UserHandle
+
+/** Encapsulates a request to show a dialog. */
+sealed class ShowDialogRequestModel {
+    data class ShowAddUserDialog(
+        val userHandle: UserHandle,
+        val isKeyguardShowing: Boolean,
+        val showEphemeralMessage: Boolean,
+    ) : ShowDialogRequestModel()
+
+    data class ShowUserCreationDialog(
+        val isGuest: Boolean,
+    ) : ShowDialogRequestModel()
+
+    data class ShowExitGuestDialog(
+        val guestUserId: Int,
+        val targetUserId: Int,
+        val isGuestEphemeral: Boolean,
+        val isKeyguardShowing: Boolean,
+        val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
+    ) : ShowDialogRequestModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
new file mode 100644
index 0000000..137de15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.legacyhelper.data
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.R
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+
+/**
+ * Defines utility functions for helping with legacy data code for users.
+ *
+ * We need these to avoid code duplication between logic inside the UserSwitcherController and in
+ * modern architecture classes such as repositories, interactors, and view-models. If we ever
+ * simplify UserSwitcherController (or delete it), the code here could be moved into its call-sites.
+ */
+object LegacyUserDataHelper {
+
+    @JvmStatic
+    fun createRecord(
+        context: Context,
+        manager: UserManager,
+        picture: Bitmap?,
+        userInfo: UserInfo,
+        isCurrent: Boolean,
+        canSwitchUsers: Boolean,
+    ): UserRecord {
+        val isGuest = userInfo.isGuest
+        return UserRecord(
+            info = userInfo,
+            picture =
+                getPicture(
+                    manager = manager,
+                    context = context,
+                    userInfo = userInfo,
+                    picture = picture,
+                ),
+            isGuest = isGuest,
+            isCurrent = isCurrent,
+            isSwitchToEnabled = canSwitchUsers || (isCurrent && !isGuest),
+        )
+    }
+
+    @JvmStatic
+    fun createRecord(
+        context: Context,
+        selectedUserId: Int,
+        actionType: UserActionModel,
+        isRestricted: Boolean,
+        isSwitchToEnabled: Boolean,
+    ): UserRecord {
+        return UserRecord(
+            isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
+            isAddUser = actionType == UserActionModel.ADD_USER,
+            isAddSupervisedUser = actionType == UserActionModel.ADD_SUPERVISED_USER,
+            isRestricted = isRestricted,
+            isSwitchToEnabled = isSwitchToEnabled,
+            enforcedAdmin =
+                getEnforcedAdmin(
+                    context = context,
+                    selectedUserId = selectedUserId,
+                ),
+        )
+    }
+
+    fun toUserActionModel(record: UserRecord): UserActionModel {
+        check(!isUser(record))
+
+        return when {
+            record.isAddUser -> UserActionModel.ADD_USER
+            record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
+            record.isGuest -> UserActionModel.ENTER_GUEST_MODE
+            else -> error("Not a known action: $record")
+        }
+    }
+
+    fun isUser(record: UserRecord): Boolean {
+        return record.info != null
+    }
+
+    private fun getEnforcedAdmin(
+        context: Context,
+        selectedUserId: Int,
+    ): EnforcedAdmin? {
+        val admin =
+            RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                context,
+                UserManager.DISALLOW_ADD_USER,
+                selectedUserId,
+            )
+                ?: return null
+
+        return if (
+            !RestrictedLockUtilsInternal.hasBaseUserRestriction(
+                context,
+                UserManager.DISALLOW_ADD_USER,
+                selectedUserId,
+            )
+        ) {
+            admin
+        } else {
+            null
+        }
+    }
+
+    private fun getPicture(
+        context: Context,
+        manager: UserManager,
+        userInfo: UserInfo,
+        picture: Bitmap?,
+    ): Bitmap? {
+        if (userInfo.isGuest) {
+            return null
+        }
+
+        if (picture != null) {
+            return picture
+        }
+
+        val unscaledOrNull = manager.getUserIcon(userInfo.id) ?: return null
+
+        val avatarSize = context.resources.getDimensionPixelSize(R.dimen.max_avatar_size)
+        return Bitmap.createScaledBitmap(
+            unscaledOrNull,
+            avatarSize,
+            avatarSize,
+            /* filter= */ true,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
index bf7977a..2095683 100644
--- a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
@@ -32,4 +32,6 @@
     val isSelected: Boolean,
     /** Whether this use is selectable. A non-selectable user cannot be switched to. */
     val isSelectable: Boolean,
+    /** Whether this model represents the guest user. */
+    val isGuest: Boolean,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index d7ad3ce..938417f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -174,6 +174,7 @@
             setOnItemClickListener { _, _, position, _ ->
                 val itemPositionExcludingHeader = position - 1
                 adapter.getItem(itemPositionExcludingHeader).onClicked()
+                dismiss()
             }
 
             show()
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
new file mode 100644
index 0000000..a9d66de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.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 com.android.systemui.user.ui.dialog
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.UserHandle
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.CreateUserActivity
+
+/** Dialog for adding a new user to the device. */
+class AddUserDialog(
+    context: Context,
+    userHandle: UserHandle,
+    isKeyguardShowing: Boolean,
+    showEphemeralMessage: Boolean,
+    private val falsingManager: FalsingManager,
+    private val broadcastSender: BroadcastSender,
+    private val dialogLaunchAnimator: DialogLaunchAnimator
+) : SystemUIDialog(context) {
+
+    private val onClickListener =
+        object : DialogInterface.OnClickListener {
+            override fun onClick(dialog: DialogInterface, which: Int) {
+                val penalty =
+                    if (which == BUTTON_NEGATIVE) {
+                        FalsingManager.NO_PENALTY
+                    } else {
+                        FalsingManager.MODERATE_PENALTY
+                    }
+                if (falsingManager.isFalseTap(penalty)) {
+                    return
+                }
+
+                if (which == BUTTON_NEUTRAL) {
+                    cancel()
+                    return
+                }
+
+                dialogLaunchAnimator.dismissStack(this@AddUserDialog)
+                if (ActivityManager.isUserAMonkey()) {
+                    return
+                }
+
+                // Use broadcast instead of ShadeController, as this dialog may have started in
+                // another
+                // process where normal dagger bindings are not available.
+                broadcastSender.sendBroadcastAsUser(
+                    Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+                    UserHandle.CURRENT
+                )
+
+                context.startActivityAsUser(
+                    CreateUserActivity.createIntentForStart(context),
+                    userHandle,
+                )
+            }
+        }
+
+    init {
+        setTitle(R.string.user_add_user_title)
+        val message =
+            context.getString(R.string.user_add_user_message_short) +
+                if (showEphemeralMessage) {
+                    context.getString(
+                        com.android.systemui.R.string.user_add_user_message_guest_remove
+                    )
+                } else {
+                    ""
+                }
+        setMessage(message)
+
+        setButton(
+            BUTTON_NEUTRAL,
+            context.getString(android.R.string.cancel),
+            onClickListener,
+        )
+
+        setButton(
+            BUTTON_POSITIVE,
+            context.getString(android.R.string.ok),
+            onClickListener,
+        )
+
+        setWindowOnTop(this, isKeyguardShowing)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
new file mode 100644
index 0000000..19ad44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ui.dialog
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.DialogInterface
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog for exiting the guest user. */
+class ExitGuestDialog(
+    context: Context,
+    private val guestUserId: Int,
+    private val isGuestEphemeral: Boolean,
+    private val targetUserId: Int,
+    isKeyguardShowing: Boolean,
+    private val falsingManager: FalsingManager,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val onExitGuestUserListener: OnExitGuestUserListener,
+) : SystemUIDialog(context) {
+
+    fun interface OnExitGuestUserListener {
+        fun onExitGuestUser(
+            @UserIdInt guestId: Int,
+            @UserIdInt targetId: Int,
+            forceRemoveGuest: Boolean,
+        )
+    }
+
+    private val onClickListener =
+        object : DialogInterface.OnClickListener {
+            override fun onClick(dialog: DialogInterface, which: Int) {
+                val penalty =
+                    if (which == BUTTON_NEGATIVE) {
+                        FalsingManager.NO_PENALTY
+                    } else {
+                        FalsingManager.MODERATE_PENALTY
+                    }
+                if (falsingManager.isFalseTap(penalty)) {
+                    return
+                }
+
+                if (isGuestEphemeral) {
+                    if (which == BUTTON_POSITIVE) {
+                        dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                        // Ephemeral guest: exit guest, guest is removed by the system
+                        // on exit, since its marked ephemeral
+                        onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, false)
+                    } else if (which == BUTTON_NEGATIVE) {
+                        // Cancel clicked, do nothing
+                        cancel()
+                    }
+                } else {
+                    when (which) {
+                        BUTTON_POSITIVE -> {
+                            dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                            // Non-ephemeral guest: exit guest, guest is not removed by the system
+                            // on exit, since its marked non-ephemeral
+                            onExitGuestUserListener.onExitGuestUser(
+                                guestUserId,
+                                targetUserId,
+                                false
+                            )
+                        }
+                        BUTTON_NEGATIVE -> {
+                            dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                            // Non-ephemeral guest: remove guest and then exit
+                            onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, true)
+                        }
+                        BUTTON_NEUTRAL -> {
+                            // Cancel clicked, do nothing
+                            cancel()
+                        }
+                    }
+                }
+            }
+        }
+
+    init {
+        if (isGuestEphemeral) {
+            setTitle(context.getString(R.string.guest_exit_dialog_title))
+            setMessage(context.getString(R.string.guest_exit_dialog_message))
+            setButton(
+                BUTTON_NEUTRAL,
+                context.getString(android.R.string.cancel),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_POSITIVE,
+                context.getString(R.string.guest_exit_dialog_button),
+                onClickListener,
+            )
+        } else {
+            setTitle(context.getString(R.string.guest_exit_dialog_title_non_ephemeral))
+            setMessage(context.getString(R.string.guest_exit_dialog_message_non_ephemeral))
+            setButton(
+                BUTTON_NEUTRAL,
+                context.getString(android.R.string.cancel),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_NEGATIVE,
+                context.getString(R.string.guest_exit_clear_data_button),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_POSITIVE,
+                context.getString(R.string.guest_exit_save_data_button),
+                onClickListener,
+            )
+        }
+        setWindowOnTop(this, isKeyguardShowing)
+        setCanceledOnTouchOutside(false)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
similarity index 61%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
rename to packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
index 99a08ab..c1d2f47 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
@@ -12,17 +12,22 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.settingslib.spaprivileged.model.app
+package com.android.systemui.user.ui.dialog
 
-import android.content.pm.ApplicationInfo
-import android.os.UserHandle
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
-val ApplicationInfo.userId: Int
-    get() = UserHandle.getUserId(uid)
+@Module
+interface UserDialogModule {
 
-val ApplicationInfo.userHandle: UserHandle
-    get() = UserHandle.getUserHandleForUid(uid)
-
-fun ApplicationInfo.toRoute() = "$packageName/$userId"
+    @Binds
+    @IntoMap
+    @ClassKey(UserSwitcherDialogCoordinator::class)
+    fun bindFeature(impl: UserSwitcherDialogCoordinator): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
new file mode 100644
index 0000000..6e7b523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ui.dialog
+
+import android.app.Dialog
+import android.content.Context
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+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.plugins.FalsingManager
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Coordinates dialogs for user switcher logic. */
+@SysUISingleton
+class UserSwitcherDialogCoordinator
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Application private val applicationScope: CoroutineScope,
+    private val falsingManager: FalsingManager,
+    private val broadcastSender: BroadcastSender,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val interactor: UserInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable(context) {
+
+    private var currentDialog: Dialog? = null
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+            return
+        }
+
+        startHandlingDialogShowRequests()
+        startHandlingDialogDismissRequests()
+    }
+
+    private fun startHandlingDialogShowRequests() {
+        applicationScope.launch {
+            interactor.dialogShowRequests.filterNotNull().collect { request ->
+                currentDialog?.let {
+                    if (it.isShowing) {
+                        it.cancel()
+                    }
+                }
+
+                currentDialog =
+                    when (request) {
+                        is ShowDialogRequestModel.ShowAddUserDialog ->
+                            AddUserDialog(
+                                context = context,
+                                userHandle = request.userHandle,
+                                isKeyguardShowing = request.isKeyguardShowing,
+                                showEphemeralMessage = request.showEphemeralMessage,
+                                falsingManager = falsingManager,
+                                broadcastSender = broadcastSender,
+                                dialogLaunchAnimator = dialogLaunchAnimator,
+                            )
+                        is ShowDialogRequestModel.ShowUserCreationDialog ->
+                            UserCreatingDialog(
+                                context,
+                                request.isGuest,
+                            )
+                        is ShowDialogRequestModel.ShowExitGuestDialog ->
+                            ExitGuestDialog(
+                                context = context,
+                                guestUserId = request.guestUserId,
+                                isGuestEphemeral = request.isGuestEphemeral,
+                                targetUserId = request.targetUserId,
+                                isKeyguardShowing = request.isKeyguardShowing,
+                                falsingManager = falsingManager,
+                                dialogLaunchAnimator = dialogLaunchAnimator,
+                                onExitGuestUserListener = request.onExitGuestUser,
+                            )
+                    }
+
+                currentDialog?.show()
+                interactor.onDialogShown()
+            }
+        }
+    }
+
+    private fun startHandlingDialogDismissRequests() {
+        applicationScope.launch {
+            interactor.dialogDismissRequests.filterNotNull().collect {
+                currentDialog?.let {
+                    if (it.isShowing) {
+                        it.cancel()
+                    }
+                }
+
+                interactor.onDialogDismissed()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 398341d..5b83df7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -21,7 +21,10 @@
 import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.R
 import com.android.systemui.common.ui.drawable.CircularDrawable
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
@@ -36,9 +39,14 @@
 class UserSwitcherViewModel
 private constructor(
     private val userInteractor: UserInteractor,
+    private val guestUserInteractor: GuestUserInteractor,
     private val powerInteractor: PowerInteractor,
+    private val featureFlags: FeatureFlags,
 ) : ViewModel() {
 
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -47,9 +55,6 @@
     val maximumUserColumns: Flow<Int> =
         users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
 
-    /** Whether the button to open the user action menu is visible. */
-    val isOpenMenuButtonVisible: Flow<Boolean> = userInteractor.actions.map { it.isNotEmpty() }
-
     private val _isMenuVisible = MutableStateFlow(false)
     /**
      * Whether the user action menu should be shown. Once the action menu is dismissed/closed, the
@@ -58,9 +63,23 @@
     val isMenuVisible: Flow<Boolean> = _isMenuVisible
     /** The user action menu. */
     val menu: Flow<List<UserActionViewModel>> =
-        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+        userInteractor.actions.map { actions ->
+            if (isNewImpl && actions.isNotEmpty()) {
+                    // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
+                    // switcher specific action that is not known to the our data source or other
+                    // features.
+                    actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+                } else {
+                    actions
+                }
+                .map { action -> toViewModel(action) }
+        }
+
+    /** Whether the button to open the user action menu is visible. */
+    val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
 
     private val hasCancelButtonBeenClicked = MutableStateFlow(false)
+    private val isFinishRequiredDueToExecutedAction = MutableStateFlow(false)
 
     /**
      * Whether the observer should finish the experience. Once consumed, [onFinished] must be called
@@ -81,6 +100,7 @@
      */
     fun onFinished() {
         hasCancelButtonBeenClicked.value = false
+        isFinishRequiredDueToExecutedAction.value = false
     }
 
     /** Notifies that the user has clicked the "open menu" button. */
@@ -120,8 +140,10 @@
             },
             // When the cancel button is clicked, we should finish.
             hasCancelButtonBeenClicked,
-        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked ->
-            selectedUserChanged || screenTurnedOff || cancelButtonClicked
+            // If an executed action told us to finish, we should finish,
+            isFinishRequiredDueToExecutedAction,
+        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
+            selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
         }
     }
 
@@ -164,13 +186,25 @@
                 } else {
                     LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
                         isGuest = model == UserActionModel.ENTER_GUEST_MODE,
-                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = userInteractor.isGuestUserResetting,
+                        isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
+                        isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
                         isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
                         isAddUser = model == UserActionModel.ADD_USER,
                     )
                 },
-            onClicked = { userInteractor.executeAction(action = model) },
+            onClicked = {
+                userInteractor.executeAction(action = model)
+                // We don't finish because we want to show a dialog over the full-screen UI and
+                // that dialog can be dismissed in case the user changes their mind and decides not
+                // to add a user.
+                //
+                // We finish for all other actions because they navigate us away from the
+                // full-screen experience or are destructive (like changing to the guest user).
+                val shouldFinish = model != UserActionModel.ADD_USER
+                if (shouldFinish) {
+                    isFinishRequiredDueToExecutedAction.value = true
+                }
+            },
         )
     }
 
@@ -186,13 +220,17 @@
     @Inject
     constructor(
         private val userInteractor: UserInteractor,
+        private val guestUserInteractor: GuestUserInteractor,
         private val powerInteractor: PowerInteractor,
+        private val featureFlags: FeatureFlags,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
             return UserSwitcherViewModel(
                 userInteractor = userInteractor,
+                guestUserInteractor = guestUserInteractor,
                 powerInteractor = powerInteractor,
+                featureFlags = featureFlags,
             )
                 as T
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d745..a925e38 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@
     private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
     private boolean mDefaultShowOperatorNameConfigLoaded;
     private boolean mDefaultShowOperatorNameConfig;
+    private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+    private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
 
     @Inject
     public CarrierConfigTracker(
@@ -207,6 +209,22 @@
     }
 
     /**
+     * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+     * the default carrier config.
+     */
+    public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+        if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+            mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+                    .getDefaultConfig().getBoolean(CarrierConfigManager
+                            .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+                    );
+            mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+        }
+
+        return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+    }
+
+    /**
      * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
      * default value if no override exists
      *
diff --git a/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt b/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt
new file mode 100644
index 0000000..ac931e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.recycler
+
+import android.graphics.Rect
+import android.view.View
+import androidx.annotation.Dimension
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * RecyclerView ItemDecorator that adds a horizontal space of the given size between items
+ * and double that space on the ends.
+ */
+class HorizontalSpacerItemDecoration(@Dimension private val offset: Int) :
+    RecyclerView.ItemDecoration() {
+
+    override fun getItemOffsets(
+        outRect: Rect,
+        view: View,
+        parent: RecyclerView,
+        state: RecyclerView.State
+    ) {
+        val position: Int = parent.getChildAdapterPosition(view)
+        val itemCount = parent.adapter?.itemCount ?: 0
+
+        val left = if (position == 0) offset * 2 else offset
+        val right = if (position == itemCount - 1) offset * 2 else offset
+
+        outRect.set(left, 0, right, 0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
new file mode 100644
index 0000000..0b8257d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.util.settings
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Kotlin extension functions for [SettingsProxy]. */
+object SettingsProxyExt {
+
+    /** Returns a flow of [Unit] that is invoked each time that content is updated. */
+    fun SettingsProxy.observerFlow(
+        vararg names: String,
+        @UserIdInt userId: Int = UserHandle.USER_CURRENT,
+    ): Flow<Unit> {
+        return conflatedCallbackFlow {
+            val observer =
+                object : ContentObserver(null) {
+                    override fun onChange(selfChange: Boolean) {
+                        trySend(Unit)
+                    }
+                }
+
+            names.forEach { name -> registerContentObserverForUser(name, observer, userId) }
+
+            awaitClose { unregisterContentObserver(observer) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 504590a..33c00fb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -66,6 +66,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
@@ -179,7 +180,8 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             CaptioningManager captioningManager,
             KeyguardManager keyguardManager,
-            ActivityManager activityManager
+            ActivityManager activityManager,
+            DumpManager dumpManager
     ) {
         mContext = context.getApplicationContext();
         mPackageManager = packageManager;
@@ -208,7 +210,7 @@
         mCaptioningManager = captioningManager;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
-
+        dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
                 .isAccessibilityVolumeStreamActive();
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 2878ad9..6b3beeb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.wallpapers;
 
+import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
+
 import android.app.WallpaperColors;
+import android.app.WallpaperManager;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
@@ -31,19 +36,27 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
+import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
 import com.android.systemui.wallpapers.gl.EglHelper;
 import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -59,16 +72,32 @@
     private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
             new RectF(0, 0, 1, 1);
     private static final boolean DEBUG = false;
+
     private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
     private final ArraySet<RectF> mColorAreas = new ArraySet<>();
     private volatile int mPages = 1;
+    private boolean mPagesComputed = false;
     private HandlerThread mWorker;
     // scaled down version
     private Bitmap mMiniBitmap;
+    private final FeatureFlags mFeatureFlags;
+
+    // used in canvasEngine to load/unload the bitmap and extract the colors
+    @Background
+    private final DelayableExecutor mBackgroundExecutor;
+    private static final int DELAY_UNLOAD_BITMAP = 2000;
+
+    @Main
+    private final Executor mMainExecutor;
 
     @Inject
-    public ImageWallpaper() {
+    public ImageWallpaper(FeatureFlags featureFlags,
+            @Background DelayableExecutor backgroundExecutor,
+            @Main Executor mainExecutor) {
         super();
+        mFeatureFlags = featureFlags;
+        mBackgroundExecutor = backgroundExecutor;
+        mMainExecutor = mainExecutor;
     }
 
     @Override
@@ -80,7 +109,7 @@
 
     @Override
     public Engine onCreateEngine() {
-        return new GLEngine();
+        return mFeatureFlags.isEnabled(USE_CANVAS_RENDERER) ? new CanvasEngine() : new GLEngine();
     }
 
     @Override
@@ -321,7 +350,6 @@
                 imgArea.left = 0;
                 imgArea.right = 1;
             }
-
             return imgArea;
         }
 
@@ -489,4 +517,391 @@
             mRenderer.dump(prefix, fd, out, args);
         }
     }
+
+
+    class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
+        private WallpaperManager mWallpaperManager;
+        private final WallpaperColorExtractor mWallpaperColorExtractor;
+        private SurfaceHolder mSurfaceHolder;
+        @VisibleForTesting
+        static final int MIN_SURFACE_WIDTH = 128;
+        @VisibleForTesting
+        static final int MIN_SURFACE_HEIGHT = 128;
+        private Bitmap mBitmap;
+
+        /*
+         * Counter to unload the bitmap as soon as possible.
+         * Before any bitmap operation, this is incremented.
+         * After an operation completion, this is decremented (synchronously),
+         * and if the count is 0, unload the bitmap
+         */
+        private int mBitmapUsages = 0;
+        private final Object mLock = new Object();
+
+        CanvasEngine() {
+            super();
+            setFixedSizeAllowed(true);
+            setShowForAllUsers(true);
+            mWallpaperColorExtractor = new WallpaperColorExtractor(
+                    mBackgroundExecutor,
+                    new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+                        @Override
+                        public void onColorsProcessed(List<RectF> regions,
+                                List<WallpaperColors> colors) {
+                            CanvasEngine.this.onColorsProcessed(regions, colors);
+                        }
+
+                        @Override
+                        public void onMiniBitmapUpdated() {
+                            CanvasEngine.this.onMiniBitmapUpdated();
+                        }
+
+                        @Override
+                        public void onActivated() {
+                            setOffsetNotificationsEnabled(true);
+                        }
+
+                        @Override
+                        public void onDeactivated() {
+                            setOffsetNotificationsEnabled(false);
+                        }
+                    });
+
+            // if the number of pages is already computed, transmit it to the color extractor
+            if (mPagesComputed) {
+                mWallpaperColorExtractor.onPageChanged(mPages);
+            }
+        }
+
+        @Override
+        public void onCreate(SurfaceHolder surfaceHolder) {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
+            if (DEBUG) {
+                Log.d(TAG, "onCreate");
+            }
+            mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+            mSurfaceHolder = surfaceHolder;
+            Rect dimensions = mWallpaperManager.peekBitmapDimensions();
+            int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
+            int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
+            mSurfaceHolder.setFixedSize(width, height);
+
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .registerDisplayListener(this, null);
+            getDisplaySizeAndUpdateColorExtractor();
+            Trace.endSection();
+        }
+
+        @Override
+        public void onDestroy() {
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .unregisterDisplayListener(this);
+            mWallpaperColorExtractor.cleanUp();
+            unloadBitmap();
+        }
+
+        @Override
+        public boolean shouldZoomOutWallpaper() {
+            return true;
+        }
+
+        @Override
+        public boolean shouldWaitForEngineShown() {
+            return true;
+        }
+
+        @Override
+        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
+            }
+        }
+
+        @Override
+        public void onSurfaceDestroyed(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.i(TAG, "onSurfaceDestroyed");
+            }
+            mSurfaceHolder = null;
+        }
+
+        @Override
+        public void onSurfaceCreated(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.i(TAG, "onSurfaceCreated");
+            }
+        }
+
+        @Override
+        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.d(TAG, "onSurfaceRedrawNeeded");
+            }
+            drawFrame();
+        }
+
+        private void drawFrame() {
+            mBackgroundExecutor.execute(this::drawFrameSynchronized);
+        }
+
+        private void drawFrameSynchronized() {
+            synchronized (mLock) {
+                drawFrameInternal();
+            }
+        }
+
+        private void drawFrameInternal() {
+            if (mSurfaceHolder == null) {
+                Log.e(TAG, "attempt to draw a frame without a valid surface");
+                return;
+            }
+
+            // load the wallpaper if not already done
+            if (!isBitmapLoaded()) {
+                loadWallpaperAndDrawFrameInternal();
+            } else {
+                mBitmapUsages++;
+
+                // drawing is done on the main thread
+                mMainExecutor.execute(() -> {
+                    drawFrameOnCanvas(mBitmap);
+                    reportEngineShown(false);
+                    unloadBitmapIfNotUsed();
+                });
+            }
+        }
+
+        @VisibleForTesting
+        void drawFrameOnCanvas(Bitmap bitmap) {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
+            // TODO change SurfaceHolder API to add wcg support
+            Canvas c = mSurfaceHolder.lockHardwareCanvas();
+            if (c != null) {
+                Rect dest = mSurfaceHolder.getSurfaceFrame();
+                try {
+                    c.drawBitmap(bitmap, null, dest, null);
+                } finally {
+                    mSurfaceHolder.unlockCanvasAndPost(c);
+                }
+            }
+            Trace.endSection();
+        }
+
+        @VisibleForTesting
+        boolean isBitmapLoaded() {
+            return mBitmap != null && !mBitmap.isRecycled();
+        }
+
+        private void unloadBitmapIfNotUsed() {
+            mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
+        }
+
+        private void unloadBitmapIfNotUsedSynchronized() {
+            synchronized (mLock) {
+                mBitmapUsages -= 1;
+                if (mBitmapUsages <= 0) {
+                    mBitmapUsages = 0;
+                    unloadBitmapInternal();
+                }
+            }
+        }
+
+        private void unloadBitmap() {
+            mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
+        }
+
+        private void unloadBitmapSynchronized() {
+            synchronized (mLock) {
+                mBitmapUsages = 0;
+                unloadBitmapInternal();
+            }
+        }
+
+        private void unloadBitmapInternal() {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
+            if (mBitmap != null) {
+                mBitmap.recycle();
+            }
+            mBitmap = null;
+
+            final Surface surface = getSurfaceHolder().getSurface();
+            surface.hwuiDestroy();
+            mWallpaperManager.forgetLoadedWallpaper();
+            Trace.endSection();
+        }
+
+        private void loadWallpaperAndDrawFrameInternal() {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+            boolean loadSuccess = false;
+            Bitmap bitmap;
+            try {
+                bitmap = mWallpaperManager.getBitmap(false);
+                if (bitmap != null
+                        && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+                    throw new RuntimeException("Wallpaper is too large to draw!");
+                }
+            } catch (RuntimeException | OutOfMemoryError exception) {
+
+                // Note that if we do fail at this, and the default wallpaper can't
+                // be loaded, we will go into a cycle. Don't do a build where the
+                // default wallpaper can't be loaded.
+                Log.w(TAG, "Unable to load wallpaper!", exception);
+                try {
+                    mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
+                } catch (IOException ex) {
+                    // now we're really screwed.
+                    Log.w(TAG, "Unable reset to default wallpaper!", ex);
+                }
+
+                try {
+                    bitmap = mWallpaperManager.getBitmap(false);
+                } catch (RuntimeException | OutOfMemoryError e) {
+                    Log.w(TAG, "Unable to load default wallpaper!", e);
+                    bitmap = null;
+                }
+            }
+
+            if (bitmap == null) {
+                Log.w(TAG, "Could not load bitmap");
+            } else if (bitmap.isRecycled()) {
+                Log.e(TAG, "Attempt to load a recycled bitmap");
+            } else if (mBitmap == bitmap) {
+                Log.e(TAG, "Loaded a bitmap that was already loaded");
+            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
+                Log.e(TAG, "Attempt to load an invalid wallpaper of length "
+                        + bitmap.getWidth() + "x" + bitmap.getHeight());
+            } else {
+                // at this point, loading is done correctly.
+                loadSuccess = true;
+                // recycle the previously loaded bitmap
+                if (mBitmap != null) {
+                    mBitmap.recycle();
+                }
+                mBitmap = bitmap;
+
+                // +2 usages for the color extraction and the delayed unload.
+                mBitmapUsages += 2;
+                recomputeColorExtractorMiniBitmap();
+                drawFrameInternal();
+
+                /*
+                 * after loading, the bitmap will be unloaded after all these conditions:
+                 *   - the frame is redrawn
+                 *   - the mini bitmap from color extractor is recomputed
+                 *   - the DELAY_UNLOAD_BITMAP has passed
+                 */
+                mBackgroundExecutor.executeDelayed(
+                        this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
+            }
+            // even if the bitmap cannot be loaded, call reportEngineShown
+            if (!loadSuccess) reportEngineShown(false);
+            Trace.endSection();
+        }
+
+        private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+            try {
+                notifyLocalColorsChanged(regions, colors);
+            } catch (RuntimeException e) {
+                Log.e(TAG, e.getMessage(), e);
+            }
+        }
+
+        @VisibleForTesting
+        void recomputeColorExtractorMiniBitmap() {
+            mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+        }
+
+        @VisibleForTesting
+        void onMiniBitmapUpdated() {
+            unloadBitmapIfNotUsed();
+        }
+
+        @Override
+        public boolean supportsLocalColorExtraction() {
+            return true;
+        }
+
+        @Override
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            // this call will activate the offset notifications
+            // if no colors were being processed before
+            mWallpaperColorExtractor.addLocalColorsAreas(regions);
+        }
+
+        @Override
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            // this call will deactivate the offset notifications
+            // if we are no longer processing colors
+            mWallpaperColorExtractor.removeLocalColorAreas(regions);
+        }
+
+        @Override
+        public void onOffsetsChanged(float xOffset, float yOffset,
+                float xOffsetStep, float yOffsetStep,
+                int xPixelOffset, int yPixelOffset) {
+            /*
+             * TODO check this formula. mPages is always >= 4, even when launcher is single-paged
+             * this formula is also used in the GL engine
+             */
+            final int pages;
+            if (xOffsetStep > 0 && xOffsetStep <= 1) {
+                pages = Math.round(1 / xOffsetStep) + 1;
+            } else {
+                pages = 1;
+            }
+            if (pages != mPages || !mPagesComputed) {
+                mPages = pages;
+                mPagesComputed = true;
+                mWallpaperColorExtractor.onPageChanged(mPages);
+            }
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {
+
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            // changes the display in the color extractor
+            // the new display dimensions will be used in the next color computation
+            if (displayId == getDisplayContext().getDisplayId()) {
+                getDisplaySizeAndUpdateColorExtractor();
+            }
+        }
+
+        private void getDisplaySizeAndUpdateColorExtractor() {
+            Rect window = getDisplayContext()
+                    .getSystemService(WindowManager.class)
+                    .getCurrentWindowMetrics()
+                    .getBounds();
+            mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+        }
+
+
+        @Override
+        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+            super.dump(prefix, fd, out, args);
+            out.print(prefix); out.print("Engine="); out.println(this);
+            out.print(prefix); out.print("valid surface=");
+            out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
+                    ? getSurfaceHolder().getSurface().isValid()
+                    : "null");
+
+            out.print(prefix); out.print("surface frame=");
+            out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
+
+            out.print(prefix); out.print("bitmap=");
+            out.println(mBitmap == null ? "null"
+                    : mBitmap.isRecycled() ? "recycled"
+                    : mBitmap.getWidth() + "x" + mBitmap.getHeight());
+
+            mWallpaperColorExtractor.dump(prefix, fd, out, args);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 0000000..e2e4555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses  a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+    private Bitmap mMiniBitmap;
+
+    @VisibleForTesting
+    static final int SMALL_SIDE = 128;
+
+    private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
+
+    private int mDisplayWidth = -1;
+    private int mDisplayHeight = -1;
+    private int mPages = -1;
+    private int mBitmapWidth = -1;
+    private int mBitmapHeight = -1;
+
+    private final Object mLock = new Object();
+
+    private final List<RectF> mPendingRegions = new ArrayList<>();
+    private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+    @Background
+    private final Executor mBackgroundExecutor;
+
+    private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+    /**
+     * Interface to handle the callbacks after the different steps of the color extraction
+     */
+    public interface WallpaperColorExtractorCallback {
+        /**
+         * Callback after the colors of new regions have been extracted
+         * @param regions the list of new regions that have been processed
+         * @param colors the resulting colors for these regions, in the same order as the regions
+         */
+        void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+        /**
+         * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+         * no longer used by the color extractor and can be safely recycled
+         */
+        void onMiniBitmapUpdated();
+
+        /**
+         * Callback to inform that the extractor has started processing colors
+         */
+        void onActivated();
+
+        /**
+         * Callback to inform that no more colors are being processed
+         */
+        void onDeactivated();
+    }
+
+    /**
+     * Creates a new color extractor.
+     * @param backgroundExecutor the executor on which the color extraction will be performed
+     * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+     *                                        the color extractor.
+     */
+    public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+            WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+        mBackgroundExecutor = backgroundExecutor;
+        mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+    }
+
+    /**
+     * Used by the outside to inform that the display size has changed.
+     * The new display size will be used in the next computations, but the current colors are
+     * not recomputed.
+     */
+    public void setDisplayDimensions(int displayWidth, int displayHeight) {
+        mBackgroundExecutor.execute(() ->
+                setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+    }
+
+    private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+        synchronized (mLock) {
+            if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            processColorsInternal();
+        }
+    }
+
+    /**
+     * @return whether color extraction is currently in use
+     */
+    private boolean isActive() {
+        return mPendingRegions.size() + mProcessedRegions.size() > 0;
+    }
+
+    /**
+     * Should be called when the wallpaper is changed.
+     * This will recompute the mini bitmap
+     * and restart the extraction of all areas
+     * @param bitmap the new wallpaper
+     */
+    public void onBitmapChanged(@NonNull Bitmap bitmap) {
+        mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+    }
+
+    private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+        synchronized (mLock) {
+            if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+                Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+                return;
+            }
+            mBitmapWidth = bitmap.getWidth();
+            mBitmapHeight = bitmap.getHeight();
+            mMiniBitmap = createMiniBitmap(bitmap);
+            mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+            recomputeColors();
+        }
+    }
+
+    /**
+     * Should be called when the number of pages is changed
+     * This will restart the extraction of all areas
+     * @param pages the total number of pages of the launcher
+     */
+    public void onPageChanged(int pages) {
+        mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+    }
+
+    private void onPageChangedSynchronized(int pages) {
+        synchronized (mLock) {
+            if (mPages == pages) return;
+            mPages = pages;
+            if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+                recomputeColors();
+            }
+        }
+    }
+
+    // helper to recompute colors, to be called in synchronized methods
+    private void recomputeColors() {
+        mPendingRegions.addAll(mProcessedRegions);
+        mProcessedRegions.clear();
+        processColorsInternal();
+    }
+
+    /**
+     * Add new regions to extract
+     * This will trigger the color extraction and call the callback only for these new regions
+     * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+     */
+    public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+        if (regions.size() > 0) {
+            mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+        } else {
+            Log.w(TAG, "Attempt to add colors with an empty list");
+        }
+    }
+
+    private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+        synchronized (mLock) {
+            boolean wasActive = isActive();
+            mPendingRegions.addAll(regions);
+            if (!wasActive && isActive()) {
+                mWallpaperColorExtractorCallback.onActivated();
+            }
+            processColorsInternal();
+        }
+    }
+
+    /**
+     * Remove regions to extract. If a color extraction is ongoing does not stop it.
+     * But if there are subsequent changes that restart the extraction, the removed regions
+     * will not be recomputed.
+     * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+     */
+    public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+        mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+    }
+
+    private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+        synchronized (mLock) {
+            boolean wasActive = isActive();
+            mPendingRegions.removeAll(regions);
+            regions.forEach(mProcessedRegions::remove);
+            if (wasActive && !isActive()) {
+                mWallpaperColorExtractorCallback.onDeactivated();
+            }
+        }
+    }
+
+    /**
+     * Clean up the memory (in particular, the mini bitmap) used by this class.
+     */
+    public void cleanUp() {
+        mBackgroundExecutor.execute(this::cleanUpSynchronized);
+    }
+
+    private void cleanUpSynchronized() {
+        synchronized (mLock) {
+            if (mMiniBitmap != null) {
+                mMiniBitmap.recycle();
+                mMiniBitmap = null;
+            }
+            mProcessedRegions.clear();
+            mPendingRegions.clear();
+        }
+    }
+
+    private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+        Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+        // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+        int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+        float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+        Bitmap result = createMiniBitmap(bitmap,
+                (int) (scale * bitmap.getWidth()),
+                (int) (scale * bitmap.getHeight()));
+        Trace.endSection();
+        return result;
+    }
+
+    @VisibleForTesting
+    Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+        return Bitmap.createScaledBitmap(bitmap, width, height, false);
+    }
+
+    private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+        RectF imageArea = pageToImgRect(area);
+        if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+            return null;
+        }
+        Rect subImage = new Rect(
+                (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+                (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+                (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+                (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+        if (subImage.isEmpty()) {
+            // Do not notify client. treat it as too small to sample
+            return null;
+        }
+        return getLocalWallpaperColors(subImage);
+    }
+
+    @VisibleForTesting
+    WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+        Assert.isNotMainThread();
+        Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+                subImage.left, subImage.top, subImage.width(), subImage.height());
+        return WallpaperColors.fromBitmap(colorImg);
+    }
+
+    /**
+     * Transform the logical coordinates into wallpaper coordinates.
+     *
+     * Logical coordinates are organised such that the various pages are non-overlapping. So,
+     * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+     *
+     * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+     * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+     * pages increase.
+     * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+     * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+     * (1-Wr)].
+     */
+    private RectF pageToImgRect(RectF area) {
+        // Width of a page for the caller of this API.
+        float virtualPageWidth = 1f / (float) mPages;
+        float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+        float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+        int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+        if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+            Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+            return null;
+        }
+
+        RectF imgArea = new RectF();
+        imgArea.bottom = area.bottom;
+        imgArea.top = area.top;
+
+        float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+        float mappedScreenWidth = mDisplayWidth * imageScale;
+        float pageWidth = Math.min(1.0f,
+                mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+        float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+        imgArea.left = MathUtils.constrain(
+                leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+        imgArea.right = MathUtils.constrain(
+                rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+        if (imgArea.left > imgArea.right) {
+            // take full page
+            imgArea.left = 0;
+            imgArea.right = 1;
+        }
+        return imgArea;
+    }
+
+    /**
+     * Extract the colors from the pending regions,
+     * then notify the callback with the resulting colors for these regions
+     * This method should only be called synchronously
+     */
+    private void processColorsInternal() {
+        /*
+         * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+         * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+         * will be called again when the bitmap is loaded and the miniBitmap is computed.
+         */
+        if (mMiniBitmap == null || mMiniBitmap.isRecycled())  return;
+
+        /*
+         * if the screen size or number of pages is not yet known, exit
+         * the function will be called again once the screen size and page are known
+         */
+        if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+        Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+        List<WallpaperColors> processedColors = new ArrayList<>();
+        for (int i = 0; i < mPendingRegions.size(); i++) {
+            RectF nextArea = mPendingRegions.get(i);
+            WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+            mProcessedRegions.add(nextArea);
+            processedColors.add(colors);
+        }
+        List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+        mPendingRegions.clear();
+        Trace.endSection();
+
+        mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+    }
+
+    /**
+     * Called to dump current state.
+     * @param prefix prefix.
+     * @param fd fd.
+     * @param out out.
+     * @param args args.
+     */
+    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+        out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+        out.print(prefix); out.print("mPages="); out.println(mPages);
+
+        out.print(prefix); out.print("bitmap dimensions=");
+        out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+        out.print(prefix); out.print("bitmap=");
+        out.println(mMiniBitmap == null ? "null"
+                : mMiniBitmap.isRecycled() ? "recycled"
+                : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+        out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+        out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3961a8b..3472cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -55,6 +55,7 @@
 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.floating.FloatingTasks;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -106,6 +107,7 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<FloatingTasks> mFloatingTasksOptional;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -166,6 +168,7 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
+            Optional<FloatingTasks> floatingTasksOptional,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -190,6 +193,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mUserTracker = userTracker;
+        mFloatingTasksOptional = floatingTasksOptional;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
 
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 0000000..3c418ed
--- /dev/null
+++ b/packages/SystemUI/tests/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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "SystemUITests",
+
+    dxflags: ["--multi-dex"],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    static_libs: ["SystemUI-tests"],
+    compile_multilib: "both",
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+    ],
+    aaptflags: [
+        "--extra-packages com.android.systemui",
+    ],
+
+    // sign this with platform cert, so this test is allowed to inject key events into
+    // UI it doesn't own. This is necessary to allow screenshots to be taken
+    certificate: "platform",
+
+    additional_manifests: ["AndroidManifest.xml"],
+    manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d..0000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
-    libdexmakerjvmtiagent \
-    libmultiplejvmtiagentsinterferenceagent \
-    libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
-    telephony-common \
-    android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-#   Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-#   Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
-    include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 2714cf4..aca60c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,14 +86,12 @@
     becauseCannotSkipBouncer = false,
     biometricSettingEnabledForUser = false,
     bouncerFullyShown = false,
-    bouncerIsOrWillShow = false,
-    onlyFaceEnrolled = false,
     faceAuthenticated = false,
     faceDisabled = false,
     faceLockedOut = false,
     fpLockedOut = false,
     goingToSleep = false,
-    keyguardAwakeExcludingBouncerShowing = false,
+    keyguardAwake = false,
     keyguardGoingAway = false,
     listeningForFaceAssistant = false,
     occludingAppRequestingFaceAuth = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 28e99da..c1036e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -116,9 +116,7 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
 
         when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
-        when(mUserSwitcherController.getKeyguardStateController())
-                .thenReturn(mKeyguardStateController);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
 
         mScreenWidth = getUiDevice().getDisplayWidth();
         mFakeMeasureSpec = View
@@ -413,7 +411,7 @@
                     0 /* flags */);
             users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
                     false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
-                    false /* isAddSupervisedUser */));
+                    false /* isAddSupervisedUser */, null /* enforcedAdmin */));
         }
         return users;
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e3c7128..d2116b9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -60,6 +60,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -80,13 +81,13 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.dreams.IDreamManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -171,6 +172,8 @@
     @Mock
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
+    private IDreamManager mDreamManager;
+    @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private SubscriptionManager mSubscriptionManager;
@@ -179,6 +182,8 @@
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
+    private SensorPrivacyManager mSensorPrivacyManager;
+    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private AuthController mAuthController;
@@ -213,8 +218,6 @@
             mBiometricEnabledCallbackArgCaptor;
     @Captor
     private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor;
 
     // Direct executor
     private final Executor mBackgroundExecutor = Runnable::run;
@@ -222,7 +225,6 @@
     private TestableLooper mTestableLooper;
     private Handler mHandler;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private TestableContext mSpiedContext;
     private MockitoSession mMockitoSession;
     private StatusBarStateController.StateListener mStatusBarStateListener;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
@@ -231,9 +233,6 @@
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mSpiedContext = spy(mContext);
-        when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
-        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
         when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
         when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -282,14 +281,6 @@
                 .thenReturn(new ServiceState());
         when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
-        mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
-        mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
-        mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
-        mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager);
-        mSpiedContext.addMockSystemService(UserManager.class, mUserManager);
-        mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
-        mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
-        mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
 
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(SubscriptionManager.class)
@@ -304,7 +295,7 @@
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         verify(mBiometricManager)
                 .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -359,7 +350,7 @@
         when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
         when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
 
-        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mContext);
 
         mTestableLooper.processAllMessages();
 
@@ -597,13 +588,11 @@
 
     @Test
     public void testTriesToAuthenticate_whenBouncer() {
-        fingerprintIsNotEnrolled();
-        faceAuthEnabled();
         setKeyguardBouncerVisibility(true);
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
-        verify(mFaceManager, atLeastOnce()).isHardwareDetected();
-        verify(mFaceManager, atLeastOnce()).hasEnrolledTemplates(anyInt());
+        verify(mFaceManager).isHardwareDetected();
+        verify(mFaceManager).hasEnrolledTemplates(anyInt());
     }
 
     @Test
@@ -1207,9 +1196,9 @@
     @Test
     public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.addMockSystemService(FaceManager.class, null);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mFaceManager = null;
+
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
@@ -1238,9 +1227,7 @@
     public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
             throws RemoteException {
         // Face auth should run when the following is true.
-        faceAuthEnabled();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         keyguardNotGoingAway();
         currentUserIsPrimary();
         strongAuthNotRequired();
@@ -1265,9 +1252,9 @@
         // This disables face auth
         when(mUserManager.isPrimaryUser()).thenReturn(false);
         mKeyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(mSpiedContext);
+                new TestableKeyguardUpdateMonitor(mContext);
 
-        // Preconditions for face auth to run
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         strongAuthNotRequired();
@@ -1284,7 +1271,7 @@
     @Test
     public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         currentUserIsPrimary();
@@ -1305,11 +1292,8 @@
     @Test
     public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1329,11 +1313,9 @@
     @Test
     public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1352,11 +1334,8 @@
     @Test
     public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1375,7 +1354,7 @@
     @Test
     public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
             throws RemoteException {
-        // Preconditions for face auth to run
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         currentUserIsPrimary();
@@ -1398,8 +1377,7 @@
     @Test
     public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
@@ -1411,7 +1389,6 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
 
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         mTestableLooper.processAllMessages();
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1420,7 +1397,7 @@
     @Test
     public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue()
             throws RemoteException {
-        // Preconditions for face auth to run
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
@@ -1446,7 +1423,6 @@
         biometricsNotDisabledThroughDevicePolicyManager();
         biometricsEnabledForCurrentUser();
         userNotCurrentlySwitching();
-        bouncerFullyVisible();
 
         statusBarShadeIsLocked();
         mTestableLooper.processAllMessages();
@@ -1460,9 +1436,6 @@
         keyguardIsVisible();
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
         statusBarShadeIsNotLocked();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-        bouncerNotFullyVisible();
-
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
     }
 
@@ -1503,6 +1476,27 @@
     }
 
     @Test
+    public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+            throws RemoteException {
+        // Preconditions for face auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        deviceNotGoingToSleep();
+        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        deviceGoingToSleep();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    @Test
     public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
@@ -1524,44 +1518,6 @@
     }
 
     @Test
-    public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth()
-            throws RemoteException {
-        // Both fingerprint and face are enrolled by default
-        // Preconditions for face auth to run
-        keyguardNotGoingAway();
-        currentUserIsPrimary();
-        currentUserDoesNotHaveTrust();
-        biometricsNotDisabledThroughDevicePolicyManager();
-        biometricsEnabledForCurrentUser();
-        userNotCurrentlySwitching();
-        deviceNotGoingToSleep();
-        deviceIsInteractive();
-        statusBarShadeIsNotLocked();
-        keyguardIsVisible();
-
-        mTestableLooper.processAllMessages();
-        clearInvocations(mUiEventLogger);
-
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
-
-        mKeyguardUpdateMonitor.requestFaceAuth(true,
-                FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-
-        verify(mFaceManager).authenticate(any(),
-                mCancellationSignalCaptor.capture(),
-                mAuthenticationCallbackCaptor.capture(),
-                any(),
-                anyInt(),
-                anyBoolean());
-        CancellationSignal cancelSignal = mCancellationSignalCaptor.getValue();
-
-        bouncerWillBeVisibleSoon();
-        mTestableLooper.processAllMessages();
-
-        assertThat(cancelSignal.isCanceled()).isTrue();
-    }
-
-    @Test
     public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
@@ -1586,15 +1542,16 @@
         verify(mHandler, times(1)).removeCallbacks(mKeyguardUpdateMonitor.mFpCancelNotReceived);
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
     }
 
     @Test
     public void testFingerAcquired_wakesUpPowerManager() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.getOrCreateTestableResources().addOverride(
+        mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, true);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         fingerprintAcquireStart();
 
         verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
@@ -1603,9 +1560,9 @@
     @Test
     public void testFingerAcquired_doesNotWakeUpPowerManager() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.getOrCreateTestableResources().addOverride(
+        mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, false);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         fingerprintAcquireStart();
 
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -1624,21 +1581,6 @@
                 .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
     }
 
-    private void faceAuthEnabled() {
-        // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
-        // face manager mock wire-up in setup()
-        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
-    }
-
-    private void fingerprintIsNotEnrolled() {
-        when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
-        // This updates the cached fingerprint state.
-        // There is no straightforward API to update the fingerprint state.
-        // It currently works updates after enrollment changes because something else invokes
-        // startListeningForFingerprint(), which internally calls this method.
-        mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
-    }
-
     private void statusBarShadeIsNotLocked() {
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
     }
@@ -1741,23 +1683,18 @@
         mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
     }
 
+    private void deviceGoingToSleep() {
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
+    }
+
     private void deviceIsInteractive() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
     }
 
-    private void bouncerNotFullyVisible() {
-        setKeyguardBouncerVisibility(false);
-    }
-
     private void bouncerFullyVisible() {
         setKeyguardBouncerVisibility(true);
     }
 
-    private void bouncerWillBeVisibleSoon() {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, false);
-        mTestableLooper.processAllMessages();
-    }
-
     private void setKeyguardBouncerVisibility(boolean isVisible) {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
@@ -1799,7 +1736,9 @@
                     mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
                     mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
-                    mPowerManager);
+                    mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
+                    mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
+                    mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
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 4a5b23c..d52612b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -322,6 +322,13 @@
     }
 
     @Test
+    fun testLayoutParams_hasShowWhenLockedFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
+                .isTrue()
+    }
+
+    @Test
     fun testLayoutParams_hasDimbehindWindowFlag() {
         val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
         val lpFlags = layoutParams.flags
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 5c564e6..baeabc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -17,13 +17,23 @@
 package com.android.systemui.biometrics
 
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.*
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-import android.view.*
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.Surface
 import android.view.Surface.Rotation
+import android.view.View
+import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -32,11 +42,11 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
@@ -52,8 +62,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val REQUEST_ID = 2L
 
@@ -75,7 +85,7 @@
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var accessibilityManager: AccessibilityManager
     @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var panelExpansionStateManager: PanelExpansionStateManager
+    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -117,7 +127,7 @@
     private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
         controllerOverlay = UdfpsControllerOverlay(
             context, fingerprintManager, inflater, windowManager, accessibilityManager,
-            statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
+            statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
             unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
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 a6c0539..3be1a56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -70,12 +71,12 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.Execution;
@@ -244,7 +245,7 @@
                 mWindowManager,
                 mStatusBarStateController,
                 mFgExecutor,
-                new PanelExpansionStateManager(),
+                new ShadeExpansionStateManager(),
                 mStatusBarKeyguardViewManager,
                 mDumpManager,
                 mKeyguardUpdateMonitor,
@@ -687,6 +688,58 @@
     }
 
     @Test
+    public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+        // GIVEN AOD interrupt
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mScreenObserver.onScreenTurnedOn();
+        mFgExecutor.runAllReady();
+        mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+        mFgExecutor.runAllReady();
+
+        // Configure UdfpsView to accept the ACTION_UP event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+        // WHEN ACTION_UP is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+        mBiometricsExecutor.runAllReady();
+        upEvent.recycle();
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+        // WHEN ACTION_DOWN is received
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricsExecutor.runAllReady();
+        downEvent.recycle();
+
+        // WHEN ACTION_MOVE is received
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricsExecutor.runAllReady();
+        moveEvent.recycle();
+        mFgExecutor.runAllReady();
+
+        // Configure UdfpsView to accept the finger up event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+        // WHEN it times out
+        mFgExecutor.advanceClockToNext();
+        mFgExecutor.runAllReady();
+
+        // THEN the display should be unconfigured once. If the timeout action is not
+        // cancelled, the display would be unconfigured twice which would cause two
+        // FP attempts.
+        verify(mUdfpsView, times(1)).unconfigureDisplay();
+    }
+
+    @Test
     public void aodInterruptScreenOff() throws RemoteException {
         // GIVEN screen off
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index b61bda8..c0f9c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -41,14 +41,14 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -76,7 +76,7 @@
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
-    private PanelExpansionStateManager mPanelExpansionStateManager;
+    private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
@@ -109,8 +109,8 @@
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
     private StatusBarStateController.StateListener mStatusBarStateListener;
 
-    @Captor private ArgumentCaptor<PanelExpansionListener> mExpansionListenerCaptor;
-    private List<PanelExpansionListener> mExpansionListeners;
+    @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+    private List<ShadeExpansionListener> mExpansionListeners;
 
     @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
             mAltAuthInterceptorCaptor;
@@ -130,7 +130,7 @@
         mController = new UdfpsKeyguardViewController(
                 mView,
                 mStatusBarStateController,
-                mPanelExpansionStateManager,
+                mShadeExpansionStateManager,
                 mStatusBarKeyguardViewManager,
                 mKeyguardUpdateMonitor,
                 mDumpManager,
@@ -182,8 +182,8 @@
         mController.onViewDetached();
 
         verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
-        for (PanelExpansionListener listener : mExpansionListeners) {
-            verify(mPanelExpansionStateManager).removeExpansionListener(listener);
+        for (ShadeExpansionListener listener : mExpansionListeners) {
+            verify(mShadeExpansionStateManager).removeExpansionListener(listener);
         }
         verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
     }
@@ -513,7 +513,7 @@
     }
 
     private void captureStatusBarExpansionListeners() {
-        verify(mPanelExpansionStateManager, times(2))
+        verify(mShadeExpansionStateManager, times(2))
                 .addExpansionListener(mExpansionListenerCaptor.capture());
         // first (index=0) is from super class, UdfpsAnimationViewController.
         // second (index=1) is from UdfpsKeyguardViewController
@@ -521,10 +521,10 @@
     }
 
     private void updateStatusBarExpansion(float fraction, boolean expanded) {
-        PanelExpansionChangeEvent event =
-                new PanelExpansionChangeEvent(
+        ShadeExpansionChangeEvent event =
+                new ShadeExpansionChangeEvent(
                         fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
-        for (PanelExpansionListener listener : mExpansionListeners) {
+        for (ShadeExpansionListener listener : mExpansionListeners) {
             listener.onPanelExpansionChanged(event);
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
index 8e00d10..faa5db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
@@ -17,7 +17,7 @@
 package com.android.systemui.classifier;
 
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
-import static com.android.systemui.classifier.Classifier.QS_SWIPE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -106,9 +106,9 @@
     }
 
     @Test
-    public void testPass_QsSwipeAlwaysPasses() {
+    public void testPass_QsSwipeSideAlwaysPasses() {
         mClassifier.onTouchEvent(appendDownEvent(1, 1));
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 1).isFalse())
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 1).isFalse())
                 .isFalse();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
index 1d61e29..d70d6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
@@ -22,7 +22,8 @@
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
 import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
-import static com.android.systemui.classifier.Classifier.QS_SWIPE;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
+import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
@@ -323,44 +324,86 @@
     }
 
     @Test
-    public void testPass_QsSwipe() {
+    public void testPass_QsSwipeSide() {
         when(mDataProvider.isVertical()).thenReturn(false);
 
         when(mDataProvider.isUp()).thenReturn(false);  // up and right should cause no effect.
         when(mDataProvider.isRight()).thenReturn(false);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse();
 
         when(mDataProvider.isUp()).thenReturn(true);
         when(mDataProvider.isRight()).thenReturn(false);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse();
 
         when(mDataProvider.isUp()).thenReturn(false);
         when(mDataProvider.isRight()).thenReturn(true);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse();
 
         when(mDataProvider.isUp()).thenReturn(true);
         when(mDataProvider.isRight()).thenReturn(true);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isFalse();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isFalse();
     }
 
     @Test
-    public void testFalse_QsSwipe() {
+    public void testFalse_QsSwipeSide() {
         when(mDataProvider.isVertical()).thenReturn(true);
 
         when(mDataProvider.isUp()).thenReturn(false);  // up and right should cause no effect.
         when(mDataProvider.isRight()).thenReturn(false);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue();
 
         when(mDataProvider.isUp()).thenReturn(true);
         when(mDataProvider.isRight()).thenReturn(false);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue();
 
         when(mDataProvider.isUp()).thenReturn(false);
         when(mDataProvider.isRight()).thenReturn(true);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue();
 
         when(mDataProvider.isUp()).thenReturn(true);
         when(mDataProvider.isRight()).thenReturn(true);
-        assertThat(mClassifier.classifyGesture(QS_SWIPE, 0.5, 0).isFalse()).isTrue();
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_SIDE, 0.5, 0).isFalse()).isTrue();
+    }
+
+    @Test
+    public void testPass_QsNestedSwipe() {
+        when(mDataProvider.isVertical()).thenReturn(true);
+
+        when(mDataProvider.isUp()).thenReturn(false);  // up and right should cause no effect.
+        when(mDataProvider.isRight()).thenReturn(false);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse();
+
+        when(mDataProvider.isUp()).thenReturn(true);
+        when(mDataProvider.isRight()).thenReturn(false);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse();
+
+        when(mDataProvider.isUp()).thenReturn(false);
+        when(mDataProvider.isRight()).thenReturn(true);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse();
+
+        when(mDataProvider.isUp()).thenReturn(true);
+        when(mDataProvider.isRight()).thenReturn(true);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isFalse();
+    }
+
+    @Test
+    public void testFalse_QsNestedSwipe() {
+        when(mDataProvider.isVertical()).thenReturn(false);
+
+        when(mDataProvider.isUp()).thenReturn(false);  // up and right should cause no effect.
+        when(mDataProvider.isRight()).thenReturn(false);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
+
+        when(mDataProvider.isUp()).thenReturn(true);
+        when(mDataProvider.isRight()).thenReturn(false);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
+
+        when(mDataProvider.isUp()).thenReturn(false);
+        when(mDataProvider.isRight()).thenReturn(true);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
+
+        when(mDataProvider.isUp()).thenReturn(true);
+        when(mDataProvider.isRight()).thenReturn(true);
+        assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 08fe7c4..2a4c0eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.clipboardoverlay;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import android.text.SpannableString;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -129,6 +130,18 @@
         assertEquals("image/png", target.getType());
     }
 
+    @Test
+    public void test_getShareIntent_spannableText() {
+        ClipData clipData = ClipData.newPlainText("Test", new SpannableString("Test Item"));
+        Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+
+        assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
+        assertFlags(intent, EXTERNAL_INTENT_FLAGS);
+        Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+        assertEquals("Test Item", target.getStringExtra(Intent.EXTRA_TEXT));
+        assertEquals("text/plain", target.getType());
+    }
+
     // Assert that the given flags are set
     private void assertFlags(Intent intent, int flags) {
         assertTrue((intent.getFlags() & flags) == flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6436981..781dc15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -144,6 +144,12 @@
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
+        ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doAnswer(invocation ->
+                when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+        ).when(mHost).setPulsePending(boolCaptor.capture());
+
+        when(mHost.isPulsingBlocked()).thenReturn(false);
         mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
         captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
         mProximitySensor.alertListeners();
@@ -160,6 +166,29 @@
     }
 
     @Test
+    public void testOnNotification_noPulseIfPulseIsNotPendingAnymore() {
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+        ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+        doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
+
+        mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
+        mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+        clearInvocations(mMachine);
+        when(mHost.isPulsingBlocked()).thenReturn(false);
+
+        // GIVEN pulsePending = false
+        when(mHost.isPulsePending()).thenReturn(false);
+
+        // WHEN prox check returns FAR
+        mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
+        captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
+        mProximitySensor.alertListeners();
+
+        // THEN don't request pulse because the pending pulse was abandoned early
+        verify(mMachine, never()).requestPulse(anyInt());
+    }
+
+    @Test
     public void testTransitionTo_disablesAndEnablesTouchSensors() {
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
 
@@ -237,6 +266,11 @@
         when(mSessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD))
                 .thenReturn(keyguardSessionId);
 
+        ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doAnswer(invocation ->
+                when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+        ).when(mHost).setPulsePending(boolCaptor.capture());
+
         // WHEN quick pick up is triggered
         mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
 
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 d70467d..c5a7de4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
@@ -88,6 +89,9 @@
     @Mock
     ViewRootImpl mViewRoot;
 
+    @Mock
+    BouncerCallbackInteractor mBouncerCallbackInteractor;
+
     DreamOverlayContainerViewController mController;
 
     @Before
@@ -110,7 +114,8 @@
                 mResources,
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
-                MILLIS_UNTIL_FULL_JITTER);
+                MILLIS_UNTIL_FULL_JITTER,
+                mBouncerCallbackInteractor);
     }
 
     @Test
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 9d4275e..eec33ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -23,8 +23,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
@@ -57,6 +59,8 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamOverlayServiceTest extends SysuiTestCase {
+    private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
+            "lowlight");
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
 
@@ -129,7 +133,8 @@
                 mDreamOverlayComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
-                mUiEventLogger);
+                mUiEventLogger,
+                LOW_LIGHT_COMPONENT);
     }
 
     @Test
@@ -204,6 +209,22 @@
     }
 
     @Test
+    public void testLowLightSetByIntentExtra() throws RemoteException {
+        final Intent intent = new Intent();
+        intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT);
+
+        final IBinder proxy = mService.onBind(intent);
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mStateController).setLowLightActive(true);
+    }
+
+    @Test
     public void testDestroy() {
         mService.onDestroy();
         mMainExecutor.runAllReady();
@@ -211,6 +232,7 @@
         verify(mKeyguardUpdateMonitor).removeCallback(any());
         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
         verify(mStateController).setOverlayActive(false);
+        verify(mStateController).setLowLightActive(false);
     }
 
     @Test
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 2adf285..d1d32a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -218,4 +218,20 @@
         assertThat(stateController.getComplications(true).contains(complication))
                 .isTrue();
     }
+
+    @Test
+    public void testNotifyLowLightChanged() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor);
+
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        assertThat(stateController.isLowLightActive()).isFalse();
+
+        stateController.setLowLightActive(true);
+
+        mExecutor.runAllReady();
+        verify(mCallback, times(1)).onStateChanged();
+        assertThat(stateController.isLowLightActive()).isTrue();
+    }
 }
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 4ebae98..aa02178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -101,6 +102,8 @@
     DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
     @Mock
     View mStatusBarItemView;
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
 
     private final Executor mMainExecutor = Runnable::run;
 
@@ -126,7 +129,8 @@
                 Optional.of(mDreamOverlayNotificationCountProvider),
                 mZenModeController,
                 mStatusBarWindowStateController,
-                mDreamOverlayStatusBarItemsProvider);
+                mDreamOverlayStatusBarItemsProvider,
+                mDreamOverlayStateController);
     }
 
     @Test
@@ -137,6 +141,7 @@
         verify(mZenModeController).addCallback(any());
         verify(mDreamOverlayNotificationCountProvider).addCallback(any());
         verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());
+        verify(mDreamOverlayStateController).addCallback(any());
     }
 
     @Test
@@ -266,7 +271,8 @@
                 Optional.empty(),
                 mZenModeController,
                 mStatusBarWindowStateController,
-                mDreamOverlayStatusBarItemsProvider);
+                mDreamOverlayStatusBarItemsProvider,
+                mDreamOverlayStateController);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
@@ -305,6 +311,7 @@
         verify(mZenModeController).removeCallback(any());
         verify(mDreamOverlayNotificationCountProvider).removeCallback(any());
         verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any());
+        verify(mDreamOverlayStateController).removeCallback(any());
     }
 
     @Test
@@ -458,6 +465,7 @@
     @Test
     public void testStatusBarShownWhenSystemStatusBarHidden() {
         mController.onViewAttached();
+        reset(mView);
 
         final ArgumentCaptor<StatusBarWindowStateListener>
                 callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
@@ -471,6 +479,7 @@
     public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
         mController.onViewAttached();
         mController.onViewDetached();
+        reset(mView);
 
         final ArgumentCaptor<StatusBarWindowStateListener>
                 callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
@@ -493,4 +502,21 @@
 
         verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView));
     }
+
+    @Test
+    public void testLowLightHidesStatusBar() {
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
+        mController.onViewAttached();
+
+        verify(mView).setVisibility(View.INVISIBLE);
+        reset(mView);
+
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onStateChanged();
+
+        verify(mView).setVisibility(View.VISIBLE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 04ff7ae..db6082d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -29,9 +29,12 @@
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
@@ -40,6 +43,7 @@
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,6 +83,15 @@
     @Captor
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
+    @Mock
+    private ImageView mView;
+
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    @Mock
+    UiEventLogger mUiEventLogger;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -151,6 +164,30 @@
         verify(mDreamOverlayStateController).addComplication(mComplication);
     }
 
+    /**
+     * Ensures clicking home controls chip logs UiEvent.
+     */
+    @Test
+    public void testClick_logsUiEvent() {
+        final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+                new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+                        mView,
+                        mActivityStarter,
+                        mContext,
+                        mControlsComponent,
+                        mUiEventLogger);
+        viewController.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mUiEventLogger).log(
+                DreamHomeControlsComplication.DreamHomeControlsChipViewController
+                        .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+    }
+
     private void setHaveFavorites(boolean value) {
         final List<StructureInfo> favorites = mock(List.class);
         when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index bc94440..522b5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -16,17 +16,28 @@
 
 package com.android.systemui.dreams.complication;
 
-import static org.mockito.Mockito.verify;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
 
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.dream.MediaDreamComplication;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,21 +59,52 @@
     @Mock
     private MediaDreamComplication mMediaComplication;
 
+    @Mock
+    private MediaCarouselController mMediaCarouselController;
+
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    @Mock
+    private ActivityIntentHelper mActivityIntentHelper;
+
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    @Mock
+    private NotificationLockscreenUserManager mLockscreenUserManager;
+
+    @Mock
+    private FeatureFlags mFeatureFlags;
+
+    @Mock
+    private PendingIntent mPendingIntent;
+
+    private final Intent mIntent = new Intent("android.test.TEST_ACTION");
+    private final Integer mCurrentUserId = 99;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false);
     }
 
     /**
      * Ensures clicking media entry chip adds/removes media complication.
      */
     @Test
-    public void testClick() {
+    public void testClickToOpenUMO() {
         final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
                 new DreamMediaEntryComplication.DreamMediaEntryViewController(
                         mView,
                         mDreamOverlayStateController,
-                        mMediaComplication);
+                        mMediaComplication,
+                        mMediaCarouselController,
+                        mActivityStarter,
+                        mActivityIntentHelper,
+                        mKeyguardStateController,
+                        mLockscreenUserManager,
+                        mFeatureFlags);
 
         final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
                 ArgumentCaptor.forClass(View.OnClickListener.class);
@@ -85,10 +127,90 @@
                 new DreamMediaEntryComplication.DreamMediaEntryViewController(
                         mView,
                         mDreamOverlayStateController,
-                        mMediaComplication);
+                        mMediaComplication,
+                        mMediaCarouselController,
+                        mActivityStarter,
+                        mActivityIntentHelper,
+                        mKeyguardStateController,
+                        mLockscreenUserManager,
+                        mFeatureFlags);
 
         viewController.onViewDetached();
         verify(mView).setSelected(false);
         verify(mDreamOverlayStateController).removeComplication(mMediaComplication);
     }
+
+    /**
+     * Ensures clicking media entry chip opens media when flag is set.
+     */
+    @Test
+    public void testClickToOpenMediaOverLockscreen() {
+        when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true);
+
+        when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn(
+                mPendingIntent);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mPendingIntent.getIntent()).thenReturn(mIntent);
+        when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId);
+
+        final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
+                new DreamMediaEntryComplication.DreamMediaEntryViewController(
+                        mView,
+                        mDreamOverlayStateController,
+                        mMediaComplication,
+                        mMediaCarouselController,
+                        mActivityStarter,
+                        mActivityIntentHelper,
+                        mKeyguardStateController,
+                        mLockscreenUserManager,
+                        mFeatureFlags);
+        viewController.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn(
+                true);
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mActivityStarter).startActivity(mIntent, true, null, true);
+    }
+
+    /**
+     * Ensures clicking media entry chip opens media when flag is set.
+     */
+    @Test
+    public void testClickToOpenMediaDismissingLockscreen() {
+        when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(true);
+
+        when(mMediaCarouselController.getCurrentVisibleMediaContentIntent()).thenReturn(
+                mPendingIntent);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mPendingIntent.getIntent()).thenReturn(mIntent);
+        when(mLockscreenUserManager.getCurrentUserId()).thenReturn(mCurrentUserId);
+
+        final DreamMediaEntryComplication.DreamMediaEntryViewController viewController =
+                new DreamMediaEntryComplication.DreamMediaEntryViewController(
+                        mView,
+                        mDreamOverlayStateController,
+                        mMediaComplication,
+                        mMediaCarouselController,
+                        mActivityStarter,
+                        mActivityIntentHelper,
+                        mKeyguardStateController,
+                        mLockscreenUserManager,
+                        mFeatureFlags);
+        viewController.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        when(mActivityIntentHelper.wouldShowOverLockscreen(mIntent, mCurrentUserId)).thenReturn(
+                false);
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntent, null);
+    }
 }
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 c3fca29..4bd53c0 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
@@ -41,12 +41,12 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import org.junit.Before;
@@ -285,8 +285,8 @@
         final float dragDownAmount = event2.getY() - event1.getY();
 
         // Ensure correct expansion passed in.
-        PanelExpansionChangeEvent event =
-                new PanelExpansionChangeEvent(
+        ShadeExpansionChangeEvent event =
+                new ShadeExpansionChangeEvent(
                         expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
         verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(event);
     }
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 b42b769..8b1554c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -48,6 +48,7 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -234,6 +235,11 @@
         verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any());
     }
 
+    /**
+     * This specific test case appears to be flaky.
+     * b/249136797 tracks the task of root-causing and fixing it.
+     */
+    @FlakyTest
     @Test
     public void testPredictiveBackInvocationDismissesDialog() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836..7f55d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
 
@@ -148,8 +149,12 @@
         return false;
     }
 
-    private static void executeShellCommand(String cmd) {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+    private void executeShellCommand(String cmd) {
+        try {
+            runShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
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 ba1e168..7a15680 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
@@ -23,6 +23,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@
 import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -116,6 +116,7 @@
         val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
 
         assertThat(latest).isFalse()
+        assertThat(underTest.isKeyguardShowing()).isFalse()
 
         val captor = argumentCaptor<KeyguardStateController.Callback>()
         verify(keyguardStateController).addCallback(captor.capture())
@@ -123,10 +124,12 @@
         whenever(keyguardStateController.isShowing).thenReturn(true)
         captor.value.onKeyguardShowingChanged()
         assertThat(latest).isTrue()
+        assertThat(underTest.isKeyguardShowing()).isTrue()
 
         whenever(keyguardStateController.isShowing).thenReturn(false)
         captor.value.onKeyguardShowingChanged()
         assertThat(latest).isFalse()
+        assertThat(underTest.isKeyguardShowing()).isFalse()
 
         job.cancel()
     }
@@ -150,6 +153,21 @@
     }
 
     @Test
+    fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+        var latest: Boolean? = null
+
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+        assertThat(latest).isTrue()
+        job.cancel()
+
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+        assertThat(latest).isFalse()
+        job.cancel()
+    }
+
+    @Test
     fun dozeAmount() = runBlockingTest {
         val values = mutableListOf<Float>()
         val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..3a61c57
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+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
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BouncerCallbackInteractorTest : SysuiTestCase() {
+    private val bouncerCallbackInteractor = BouncerCallbackInteractor()
+    @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
+    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
+        bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+    }
+
+    @Test
+    fun testOnFullyShown() {
+        bouncerCallbackInteractor.dispatchFullyShown()
+        verify(bouncerExpansionCallback).onFullyShown()
+    }
+
+    @Test
+    fun testOnFullyHidden() {
+        bouncerCallbackInteractor.dispatchFullyHidden()
+        verify(bouncerExpansionCallback).onFullyHidden()
+    }
+
+    @Test
+    fun testOnExpansionChanged() {
+        bouncerCallbackInteractor.dispatchExpansionChanged(5f)
+        verify(bouncerExpansionCallback).onExpansionChanged(5f)
+    }
+
+    @Test
+    fun testOnVisibilityChanged() {
+        bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        verify(bouncerExpansionCallback).onVisibilityChanged(false)
+    }
+
+    @Test
+    fun testOnStartingToHide() {
+        bouncerCallbackInteractor.dispatchStartingToHide()
+        verify(bouncerExpansionCallback).onStartingToHide()
+    }
+
+    @Test
+    fun testOnStartingToShow() {
+        bouncerCallbackInteractor.dispatchStartingToShow()
+        verify(bouncerExpansionCallback).onStartingToShow()
+    }
+
+    @Test
+    fun testOnKeyguardReset() {
+        bouncerCallbackInteractor.dispatchReset()
+        verify(keyguardResetCallback).onKeyguardReset()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
new file mode 100644
index 0000000..e6c8dd8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+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.shared.model.BouncerCallbackActionsModel
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class BouncerInteractorTest : 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 keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
+    @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 bouncerInteractor: BouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        DejankUtils.setImmediate(true)
+        bouncerInteractor =
+            BouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                bouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                keyguardBypassController,
+                keyguardUpdateMonitor,
+            )
+        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+        `when`(repository.show.value).thenReturn(null)
+    }
+
+    @Test
+    fun testShow_isScrimmed() {
+        bouncerInteractor.show(true)
+        verify(repository).setShowMessage(null)
+        verify(repository).setOnScreenTurnedOff(false)
+        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setHide(false)
+        verify(repository).setStartingToHide(false)
+        verify(repository).setScrimmed(true)
+        verify(repository).setExpansion(EXPANSION_VISIBLE)
+        verify(repository).setShowingSoon(true)
+        verify(keyguardStateController).notifyBouncerShowing(true)
+        verify(bouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setVisible(true)
+        verify(repository).setShow(any(KeyguardBouncerModel::class.java))
+        verify(repository).setShowingSoon(false)
+    }
+
+    @Test
+    fun testShow_isNotScrimmed() {
+        verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+    }
+
+    @Test
+    fun testShow_keyguardIsDone() {
+        `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        verify(keyguardStateController, never()).notifyBouncerShowing(true)
+        verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
+    }
+
+    @Test
+    fun testHide() {
+        bouncerInteractor.hide()
+        verify(falsingCollector).onBouncerHidden()
+        verify(keyguardStateController).notifyBouncerShowing(false)
+        verify(repository).setShowingSoon(false)
+        verify(repository).setOnDismissAction(null)
+        verify(repository).setVisible(false)
+        verify(repository).setHide(true)
+        verify(repository).setShow(null)
+    }
+
+    @Test
+    fun testExpansion() {
+        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        bouncerInteractor.setExpansion(0.6f)
+        verify(repository).setExpansion(0.6f)
+        verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+    }
+
+    @Test
+    fun testExpansion_fullyShown() {
+        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+        bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+        verify(falsingCollector).onBouncerShown()
+        verify(bouncerCallbackInteractor).dispatchFullyShown()
+    }
+
+    @Test
+    fun testExpansion_fullyHidden() {
+        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+        bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+        verify(repository).setVisible(false)
+        verify(repository).setShow(null)
+        verify(falsingCollector).onBouncerHidden()
+        verify(bouncerCallbackInteractor).dispatchReset()
+        verify(bouncerCallbackInteractor).dispatchFullyHidden()
+    }
+
+    @Test
+    fun testExpansion_startingToHide() {
+        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        bouncerInteractor.setExpansion(0.1f)
+        verify(repository).setStartingToHide(true)
+        verify(bouncerCallbackInteractor).dispatchStartingToHide()
+    }
+
+    @Test
+    fun testShowMessage() {
+        bouncerInteractor.showMessage("abc", null)
+        verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
+    }
+
+    @Test
+    fun testDismissAction() {
+        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+        val cancelAction = mock(Runnable::class.java)
+        bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+        verify(repository)
+            .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+    }
+
+    @Test
+    fun testUpdateResources() {
+        bouncerInteractor.updateResources()
+        verify(repository).setResourceUpdateRequests(true)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticated() {
+        bouncerInteractor.notifyKeyguardAuthenticated(true)
+        verify(repository).setKeyguardAuthenticated(true)
+    }
+
+    @Test
+    fun testOnScreenTurnedOff() {
+        bouncerInteractor.onScreenTurnedOff()
+        verify(repository).setOnScreenTurnedOff(true)
+    }
+
+    @Test
+    fun testSetKeyguardPosition() {
+        bouncerInteractor.setKeyguardPosition(0f)
+        verify(repository).setKeyguardPosition(0f)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticatedHandled() {
+        bouncerInteractor.notifyKeyguardAuthenticatedHandled()
+        verify(repository).setKeyguardAuthenticated(null)
+    }
+
+    @Test
+    fun testNotifyUpdatedResources() {
+        bouncerInteractor.notifyUpdatedResources()
+        verify(repository).setResourceUpdateRequests(false)
+    }
+
+    @Test
+    fun testSetBackButtonEnabled() {
+        bouncerInteractor.setBackButtonEnabled(true)
+        verify(repository).setIsBackButtonEnabled(true)
+    }
+
+    @Test
+    fun testStartDisappearAnimation() {
+        val runnable = mock(Runnable::class.java)
+        bouncerInteractor.startDisappearAnimation(runnable)
+        verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testIsFullShowing() {
+        `when`(repository.isVisible.value).thenReturn(true)
+        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+        assertThat(bouncerInteractor.isFullyShowing()).isTrue()
+        `when`(repository.isVisible.value).thenReturn(false)
+        assertThat(bouncerInteractor.isFullyShowing()).isFalse()
+    }
+
+    @Test
+    fun testIsScrimmed() {
+        `when`(repository.isScrimmed.value).thenReturn(true)
+        assertThat(bouncerInteractor.isScrimmed()).isTrue()
+        `when`(repository.isScrimmed.value).thenReturn(false)
+        assertThat(bouncerInteractor.isScrimmed()).isFalse()
+    }
+
+    @Test
+    fun testIsInTransit() {
+        `when`(repository.showingSoon.value).thenReturn(true)
+        assertThat(bouncerInteractor.isInTransit()).isTrue()
+        `when`(repository.showingSoon.value).thenReturn(false)
+        assertThat(bouncerInteractor.isInTransit()).isFalse()
+        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        assertThat(bouncerInteractor.isInTransit()).isTrue()
+    }
+
+    @Test
+    fun testIsAnimatingAway() {
+        `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
+        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
+        assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
+    }
+
+    @Test
+    fun testWillDismissWithAction() {
+        `when`(repository.onDismissAction.value?.onDismissAction)
+            .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+        assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
+        `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+        assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index d4fba41..329c4db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -138,7 +138,7 @@
         assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
         val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
         assertThat(visibleState.icon).isNotNull()
-        assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+        assertThat(visibleState.icon.contentDescription).isNotNull()
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 5a3a78e..0a4478f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -21,9 +21,11 @@
 import android.service.quickaccesswallet.GetWalletCardsResponse
 import android.service.quickaccesswallet.QuickAccessWalletClient
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -69,8 +71,16 @@
         val job = underTest.state.onEach { latest = it }.launchIn(this)
 
         val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
-        assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
-        assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+        assertThat(visibleModel.icon)
+            .isEqualTo(
+                Icon.Loaded(
+                    drawable = ICON,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            res = R.string.accessibility_wallet_button,
+                        ),
+                )
+            )
         job.cancel()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c5e828e..b6d7559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -21,7 +21,8 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -34,6 +35,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Test
@@ -46,7 +48,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -55,7 +56,15 @@
 
     companion object {
         private val INTENT = Intent("some.intent.action")
-        private val DRAWABLE = mock<ContainedDrawable>()
+        private val DRAWABLE =
+            mock<Icon> {
+                whenever(this.contentDescription)
+                    .thenReturn(
+                        ContentDescription.Resource(
+                            res = CONTENT_DESCRIPTION_RESOURCE_ID,
+                        )
+                    )
+            }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
 
         @Parameters(
@@ -236,7 +245,6 @@
             state =
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = DRAWABLE,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
         )
         homeControls.onClickedResult =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 19d8412..1dd919a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,7 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -101,7 +103,6 @@
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -120,8 +121,8 @@
         val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
         assertThat(visibleModel.configKey).isEqualTo(configKey)
         assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        assertThat(visibleModel.icon.contentDescription)
+            .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
         job.cancel()
     }
 
@@ -131,7 +132,6 @@
         quickAccessWallet.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -150,8 +150,8 @@
         val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
         assertThat(visibleModel.configKey).isEqualTo(configKey)
         assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        assertThat(visibleModel.icon.contentDescription)
+            .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
         job.cancel()
     }
 
@@ -161,7 +161,6 @@
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -182,7 +181,6 @@
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = ICON,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
             )
 
@@ -197,7 +195,14 @@
         }
 
     companion object {
-        private val ICON: ContainedDrawable = mock()
+        private val ICON: Icon = mock {
+            whenever(this.contentDescription)
+                .thenReturn(
+                    ContentDescription.Resource(
+                        res = CONTENT_DESCRIPTION_RESOURCE_ID,
+                    )
+                )
+        }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
     }
 }
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 c612091..96544e7 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
@@ -21,7 +21,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -505,7 +505,6 @@
                 }
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
             } else {
                 KeyguardQuickAffordanceConfig.State.Hidden
@@ -543,7 +542,7 @@
     private data class TestConfig(
         val isVisible: Boolean,
         val isClickable: Boolean = false,
-        val icon: ContainedDrawable? = null,
+        val icon: Icon? = null,
         val canShowWhileLocked: Boolean = false,
         val intent: Intent? = null,
     ) {
@@ -555,6 +554,5 @@
     companion object {
         private const val DEFAULT_BURN_IN_OFFSET = 5
         private const val RETURNED_BURN_IN_OFFSET = 3
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 5dd1cfc..5ad3542 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.app.PendingIntent
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -29,7 +30,6 @@
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -43,8 +43,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -71,10 +71,6 @@
     @Mock lateinit var dumpManager: DumpManager
     @Mock lateinit var logger: MediaUiEventLogger
     @Mock lateinit var debugLogger: MediaCarouselControllerLogger
-    @Mock lateinit var mediaViewHolder: MediaViewHolder
-    @Mock lateinit var player: TransitionLayout
-    @Mock lateinit var recommendationViewHolder: RecommendationViewHolder
-    @Mock lateinit var recommendations: TransitionLayout
     @Mock lateinit var mediaPlayer: MediaControlPanel
     @Mock lateinit var mediaViewController: MediaViewController
     @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@@ -280,46 +276,6 @@
         verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
     }
 
-    @Test
-    fun testSetSquishinessFractionForMedia_setPlayerBottom() {
-        whenever(panel.mediaViewHolder).thenReturn(mediaViewHolder)
-        whenever(mediaViewHolder.player).thenReturn(player)
-        whenever(player.measuredHeight).thenReturn(100)
-
-        val playingLocal = Triple("playing local",
-                DATA.copy(active = true, isPlaying = true,
-                        playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
-                4500L)
-        MediaPlayerData.addMediaPlayer(playingLocal.first, playingLocal.second, panel, clock,
-                false, debugLogger)
-
-        mediaCarouselController.squishinessFraction = 0.0f
-        verify(player).bottom = 50
-        verifyNoMoreInteractions(recommendationViewHolder)
-
-        mediaCarouselController.squishinessFraction = 0.5f
-        verify(player).bottom = 75
-        verifyNoMoreInteractions(recommendationViewHolder)
-    }
-
-    @Test
-    fun testSetSquishinessFractionForRecommendation_setPlayerBottom() {
-        whenever(panel.recommendationViewHolder).thenReturn(recommendationViewHolder)
-        whenever(recommendationViewHolder.recommendations).thenReturn(recommendations)
-        whenever(recommendations.measuredHeight).thenReturn(100)
-
-        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
-                false, clock)
-
-        mediaCarouselController.squishinessFraction = 0.0f
-        verifyNoMoreInteractions(mediaViewHolder)
-        verify(recommendationViewHolder.recommendations).bottom = 50
-
-        mediaCarouselController.squishinessFraction = 0.5f
-        verifyNoMoreInteractions(mediaViewHolder)
-        verify(recommendationViewHolder.recommendations).bottom = 75
-    }
-
     fun testMediaLoaded_ScrollToActivePlayer() {
         listener.value.onMediaDataLoaded("playing local",
                 null,
@@ -366,7 +322,7 @@
                 playerIndex,
                 mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
         )
-        assertEquals( playerIndex, 0)
+        assertEquals(playerIndex, 0)
 
         // Replaying the same media player one more time.
         // And check that the card stays in its position.
@@ -402,4 +358,44 @@
         visualStabilityCallback.value.onReorderingAllowed()
         assertEquals(true, result)
     }
+
+    @Test
+    fun testGetCurrentVisibleMediaContentIntent() {
+        val clickIntent1 = mock(PendingIntent::class.java)
+        val player1 = Triple("player1",
+                DATA.copy(clickIntent = clickIntent1),
+                1000L)
+        clock.setCurrentTimeMillis(player1.third)
+        MediaPlayerData.addMediaPlayer(player1.first,
+                player1.second.copy(notificationKey = player1.first),
+                panel, clock, isSsReactivated = false)
+
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
+
+        val clickIntent2 = mock(PendingIntent::class.java)
+        val player2 = Triple("player2",
+                DATA.copy(clickIntent = clickIntent2),
+                2000L)
+        clock.setCurrentTimeMillis(player2.third)
+        MediaPlayerData.addMediaPlayer(player2.first,
+                player2.second.copy(notificationKey = player2.first),
+                panel, clock, isSsReactivated = false)
+
+        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+        // added to the front because it was active more recently.
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+
+        val clickIntent3 = mock(PendingIntent::class.java)
+        val player3 = Triple("player3",
+                DATA.copy(clickIntent = clickIntent3),
+                500L)
+        clock.setCurrentTimeMillis(player3.third)
+        MediaPlayerData.addMediaPlayer(player3.first,
+                player3.second.copy(notificationKey = player3.first),
+                panel, clock, isSsReactivated = false)
+
+        // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+        // added to the end because it was active less recently.
+        assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
deleted file mode 100644
index 1817809..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.android.systemui.media
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.animation.MeasurementInput
-import com.android.systemui.util.animation.TransitionLayout
-import com.android.systemui.util.animation.TransitionViewState
-import com.android.systemui.util.animation.WidgetState
-import junit.framework.Assert.assertTrue
-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
-import org.mockito.Mockito.`when` as whenever
-
-/**
- * Tests for {@link MediaViewController}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaViewControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var logger: MediaViewLogger
-
-    private val configurationController =
-            com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
-    private val mediaHostStatesManager = MediaHostStatesManager()
-    private lateinit var mediaViewController: MediaViewController
-    private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
-    private var transitionLayout = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
-    @Mock private lateinit var mockViewState: TransitionViewState
-    @Mock private lateinit var mockCopiedState: TransitionViewState
-    @Mock private lateinit var mockWidgetState: WidgetState
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mediaViewController = MediaViewController(
-                context,
-                configurationController,
-                mediaHostStatesManager,
-                logger
-        )
-        mediaViewController.attach(transitionLayout, MediaViewController.TYPE.PLAYER)
-    }
-
-    @Test
-    fun testObtainViewState_applySquishFraction_toTransitionViewState_height() {
-        transitionLayout.measureState = TransitionViewState().apply {
-            this.height = 100
-        }
-        mediaHostStateHolder.expansion = 1f
-        val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
-        val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
-        mediaHostStateHolder.measurementInput =
-                MeasurementInput(widthMeasureSpec, heightMeasureSpec)
-
-        // Test no squish
-        mediaHostStateHolder.squishFraction = 1f
-        assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
-
-        // Test half squish
-        mediaHostStateHolder.squishFraction = 0.5f
-        assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
-    }
-
-    @Test
-    fun testSquish_DoesNotMutateViewState() {
-        whenever(mockViewState.copy()).thenReturn(mockCopiedState)
-        whenever(mockCopiedState.widgetStates)
-            .thenReturn(mutableMapOf(R.id.album_art to mockWidgetState))
-
-        mediaViewController.squishViewState(mockViewState, 0.5f)
-        verify(mockViewState, times(1)).copy()
-        verifyNoMoreInteractions(mockViewState)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 82aa612..5973340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -26,13 +26,13 @@
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.TaskExecutor
 import androidx.test.filters.SmallTest
-
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.concurrency.FakeRepeatableExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -47,8 +47,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
     }
     @Mock private lateinit var mockController: MediaController
     @Mock private lateinit var mockTransport: MediaController.TransportControls
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var mockBar: SeekBar
     private val token1 = MediaSession.Token(1, null)
     private val token2 = MediaSession.Token(2, null)
 
@@ -78,9 +80,10 @@
     @Before
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
+        viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
         viewModel.logSeek = { }
         whenever(mockController.sessionToken).thenReturn(token1)
+        whenever(mockBar.context).thenReturn(context)
 
         // LiveData to run synchronously
         ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -454,6 +457,25 @@
     }
 
     @Test
+    fun onFalseTapOrTouch() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
+        whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        viewModel.updateController(mockController)
+        val pos = 169
+
+        viewModel.attachTouchHandlers(mockBar)
+        with(viewModel.seekBarListener) {
+            onStartTrackingTouch(mockBar)
+            onProgressChanged(mockBar, pos, true)
+            onStopTrackingTouch(mockBar)
+        }
+
+        // THEN transport controls should not be used
+        verify(mockTransport, never()).seekTo(pos.toLong())
+    }
+
+    @Test
     fun queuePollTaskWhenPlaying() {
         // GIVEN that the track is playing
         val state = PlaybackState.Builder().run {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 22ecb4b..5f64336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
@@ -102,6 +104,18 @@
     }
 
     @Test
+    public void getItemId_validPosition_returnCorrespondingId() {
+        assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaDevices.get(
+                0).getId().hashCode());
+    }
+
+    @Test
+    public void getItemId_invalidPosition_returnPosition() {
+        int invalidPosition = mMediaDevices.size() + 1;
+        assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
+    }
+
+    @Test
     public void onBindViewHolder_bindPairNew_verifyView() {
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
 
@@ -155,6 +169,33 @@
     }
 
     @Test
+    public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
+        when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+    }
+
+    @Test
+    public void onBindViewHolder_isMutingExpectedDevice_verifyView() {
+        when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+        when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+    }
+
+    @Test
     public void onBindViewHolder_initSeekbar_setsVolume() {
         when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
         when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_CURRENT_VOLUME);
@@ -165,6 +206,20 @@
     }
 
     @Test
+    public void onBindViewHolder_bindSelectableDevice_verifyView() {
+        List<MediaDevice> selectableDevices = new ArrayList<>();
+        selectableDevices.add(mMediaDevice2);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+    }
+
+    @Test
     public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
 
@@ -223,6 +278,22 @@
     }
 
     @Test
+    public void onBindViewHolder_bindGroupingDevice_verifyView() {
+        when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+        when(mMediaDevice1.getState()).thenReturn(
+                LocalMediaManager.MediaDeviceState.STATE_GROUPING);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
         when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
         when(mMediaDevice2.getState()).thenReturn(
@@ -256,6 +327,31 @@
     }
 
     @Test
+    public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() {
+        when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+        when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+        when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        mViewHolder.mContainerLayout.performClick();
+
+        verify(mMediaOutputController).cancelMuteAwaitConnection();
+    }
+
+    @Test
+    public void onItemClick_clicksSelectableDevice_triggerGrouping() {
+        List<MediaDevice> selectableDevices = new ArrayList<>();
+        selectableDevices.add(mMediaDevice2);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        mViewHolder.mContainerLayout.performClick();
+
+        verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+    }
+
+    @Test
     public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
         when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
         List<MediaDevice> selectableDevices = new ArrayList<>();
@@ -280,4 +376,14 @@
 
         assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
     }
+
+    @Test
+    public void updateColorScheme_triggerController() {
+        WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888));
+
+        mMediaOutputAdapter.updateColorScheme(wallpaperColors, true);
+
+        verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 6dcf802..cb31fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -35,6 +35,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.drawable.Icon;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
@@ -279,6 +280,203 @@
     }
 
     @Test
+    public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+        mMediaOutputController.mIsRefreshing = true;
+
+        mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+        assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+    }
+
+    @Test
+    public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
+        when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+
+        mMediaOutputController.cancelMuteAwaitConnection();
+
+        verify(mAudioManager).cancelMuteAwaitConnection(any());
+    }
+
+    @Test
+    public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() {
+        when(mAudioManager.getMutingExpectedDevice()).thenReturn(null);
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+        mMediaOutputController.cancelMuteAwaitConnection();
+
+        verify(mAudioManager, never()).cancelMuteAwaitConnection(any());
+    }
+
+    @Test
+    public void getAppSourceName_packageNameIsNull_returnsNull() {
+        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+                "",
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotifCollection, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
+        testMediaOutputController.start(mCb);
+        reset(mCb);
+
+        testMediaOutputController.getAppSourceName();
+
+        assertThat(testMediaOutputController.getAppSourceName()).isNull();
+    }
+
+    @Test
+    public void isActiveItem_deviceNotConnected_returnsFalse() {
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
+
+        assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse();
+    }
+
+    @Test
+    public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
+        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+                "",
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotifCollection, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
+        testMediaOutputController.start(mCb);
+        reset(mCb);
+
+        testMediaOutputController.getAppSourceName();
+
+        assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull();
+    }
+
+    @Test
+    public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() {
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+        mMediaOutputController.mNeedRefresh = true;
+
+        mMediaOutputController.refreshDataSetIfNeeded();
+
+        assertThat(mMediaOutputController.mNeedRefresh).isFalse();
+    }
+
+    @Test
+    public void isCurrentConnectedDeviceRemote_containsFeatures_returnsTrue() {
+        when(mMediaDevice1.getFeatures()).thenReturn(
+                ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+
+        assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue();
+    }
+
+    @Test
+    public void addDeviceToPlayMedia_triggersFromLocalMediaManager() {
+        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+                null,
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotifCollection, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
+
+        LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
+        testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
+
+        testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2);
+
+        verify(testLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2);
+    }
+
+    @Test
+    public void removeDeviceFromPlayMedia_triggersFromLocalMediaManager() {
+        MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
+                null,
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotifCollection, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
+
+        LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
+        testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
+
+        testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2);
+
+        verify(testLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2);
+    }
+
+    @Test
+    public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
+        mMediaOutputController.getDeselectableMediaDevice();
+
+        verify(mLocalMediaManager).getDeselectableMediaDevice();
+    }
+
+    @Test
+    public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() {
+        int testVolume = 10;
+        mMediaOutputController.adjustSessionVolume(testVolume);
+
+        verify(mLocalMediaManager).adjustSessionVolume(testVolume);
+    }
+
+    @Test
+    public void getSessionVolumeMax_triggersFromLocalMediaManager() {
+        mMediaOutputController.getSessionVolumeMax();
+
+        verify(mLocalMediaManager).getSessionVolumeMax();
+    }
+
+    @Test
+    public void getSessionVolume_triggersFromLocalMediaManager() {
+        mMediaOutputController.getSessionVolume();
+
+        verify(mLocalMediaManager).getSessionVolume();
+    }
+
+    @Test
+    public void getSessionName_triggersFromLocalMediaManager() {
+        mMediaOutputController.getSessionName();
+
+        verify(mLocalMediaManager).getSessionName();
+    }
+
+    @Test
+    public void releaseSession_triggersFromLocalMediaManager() {
+        mMediaOutputController.releaseSession();
+
+        verify(mLocalMediaManager).releaseSession();
+    }
+
+    @Test
+    public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() {
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+
+        mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+        assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse();
+    }
+
+    @Test
+    public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() {
+        when(mMediaDevice1.getState()).thenReturn(
+                LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+        mMediaOutputController.start(mCb);
+        reset(mCb);
+
+        mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+        assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+    }
+
+    @Test
+    public void isPlaying_stateIsNull() {
+        when(mMediaController.getPlaybackState()).thenReturn(null);
+
+        assertThat(mMediaOutputController.isPlaying()).isFalse();
+    }
+
+    @Test
     public void onSelectedDeviceStateChanged_verifyCallback() {
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
         mMediaOutputController.start(mCb);
@@ -535,6 +733,44 @@
     }
 
     @Test
+    public void getNotificationSmallIcon_withoutSmallIcon_returnsNull() {
+        final List<NotificationEntry> entryList = new ArrayList<>();
+        final NotificationEntry entry = mock(NotificationEntry.class);
+        final StatusBarNotification sbn = mock(StatusBarNotification.class);
+        final Notification notification = mock(Notification.class);
+        entryList.add(entry);
+
+        when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+        when(entry.getSbn()).thenReturn(sbn);
+        when(sbn.getNotification()).thenReturn(notification);
+        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(notification.isMediaNotification()).thenReturn(true);
+        when(notification.getSmallIcon()).thenReturn(null);
+
+        assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull();
+    }
+
+    @Test
+    public void getNotificationSmallIcon_withPackageNameAndMediaSession_returnsIconCompat() {
+        final List<NotificationEntry> entryList = new ArrayList<>();
+        final NotificationEntry entry = mock(NotificationEntry.class);
+        final StatusBarNotification sbn = mock(StatusBarNotification.class);
+        final Notification notification = mock(Notification.class);
+        final Icon icon = mock(Icon.class);
+        entryList.add(entry);
+
+        when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+        when(entry.getSbn()).thenReturn(sbn);
+        when(sbn.getNotification()).thenReturn(notification);
+        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(notification.isMediaNotification()).thenReturn(true);
+        when(notification.getSmallIcon()).thenReturn(icon);
+
+        assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf(
+                IconCompat.class);
+    }
+
+    @Test
     public void isVolumeControlEnabled_isCastWithVolumeFixed_returnsFalse() {
         when(mMediaDevice1.getDeviceType()).thenReturn(
                 MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 9557513..bae3569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -25,7 +25,10 @@
 import static org.mockito.Mockito.when;
 
 import android.app.KeyguardManager;
+import android.graphics.Bitmap;
 import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
 import android.media.MediaRoute2Info;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -43,6 +46,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -82,6 +86,8 @@
     private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
+    private final MediaDescription  mMediaDescription = mock(MediaDescription.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
@@ -100,6 +106,8 @@
         when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
         when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE);
         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE);
+        when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+        when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription);
         mMediaControllers.add(mMediaController);
         when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
 
@@ -207,6 +215,80 @@
     }
 
     @Test
+    public void getHeaderIcon_getFromMediaControllerMetaData() {
+        int testWidth = 10;
+        int testHeight = 20;
+        when(mMediaDescription.getIconBitmap())
+                .thenReturn(Bitmap.createBitmap(testWidth, testHeight, Bitmap.Config.ARGB_8888));
+
+        assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getHeight()).isEqualTo(
+                testHeight);
+        assertThat(mMediaOutputDialog.getHeaderIcon().getBitmap().getWidth()).isEqualTo(testWidth);
+    }
+
+    @Test
+    public void getHeaderText_getFromMediaControllerMetaData() {
+        String testTitle = "test title";
+        when(mMediaDescription.getTitle())
+                .thenReturn(testTitle);
+        assertThat(mMediaOutputDialog.getHeaderText().toString()).isEqualTo(testTitle);
+    }
+
+    @Test
+    public void getHeaderSubtitle_getFromMediaControllerMetaData() {
+        String testSubtitle = "test title";
+        when(mMediaDescription.getSubtitle())
+                .thenReturn(testSubtitle);
+
+        assertThat(mMediaOutputDialog.getHeaderSubtitle().toString()).isEqualTo(testSubtitle);
+    }
+
+    @Test
+    public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() {
+        String stopText = mContext.getText(R.string.keyboard_key_media_stop).toString();
+        MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+        when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
+
+        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+                mockMediaOutputController, mUiEventLogger);
+        testDialog.show();
+
+        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+    }
+
+    @Test
+    public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
+        String stopText = mContext.getText(R.string.media_output_broadcast).toString();
+        MediaDevice mMediaDevice = mock(MediaDevice.class);
+        MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+        when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true);
+        when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice);
+        when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
+        when(mockMediaOutputController.isPlaying()).thenReturn(true);
+        when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
+        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+                mockMediaOutputController, mUiEventLogger);
+        testDialog.show();
+
+        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+    }
+
+    @Test
+    public void onStopButtonClick_notPlaying_releaseSession() {
+        MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
+        when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
+        when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
+        when(mockMediaOutputController.isPlaying()).thenReturn(false);
+        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
+                mockMediaOutputController, mUiEventLogger);
+        testDialog.show();
+
+        testDialog.onStopButtonClick();
+
+        verify(mockMediaOutputController).releaseSession();
+    }
+
+    @Test
     // Check the visibility metric logging by creating a new MediaOutput dialog,
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 0bfc034..2f52950 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.media.dream;
 
-import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION;
 
 import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.any;
@@ -68,7 +68,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true);
+        when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(true);
     }
 
     @Test
@@ -137,7 +137,7 @@
 
     @Test
     public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() {
-        when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false);
+        when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false);
 
         final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
                 mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ff0faf9..098086a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -35,7 +35,9 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -43,10 +45,12 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 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.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -75,6 +79,14 @@
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var commandQueue: CommandQueue
+    @Mock
+    private lateinit var lazyFalsingManager: Lazy<FalsingManager>
+    @Mock
+    private lateinit var falsingManager: FalsingManager
+    @Mock
+    private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
+    @Mock
+    private lateinit var falsingCollector: FalsingCollector
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var fakeClock: FakeSystemClock
@@ -101,6 +113,8 @@
         senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+        whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
+        whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
 
         controllerSender = MediaTttChipControllerSender(
             commandQueue,
@@ -111,7 +125,9 @@
             accessibilityManager,
             configurationController,
             powerManager,
-            senderUiEventLogger
+            senderUiEventLogger,
+            lazyFalsingManager,
+            lazyFalsingCollector
         )
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -417,6 +433,38 @@
     }
 
     @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isFalse()
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isTrue()
+    }
+
+    @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
new file mode 100644
index 0000000..00b1f32
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -0,0 +1,120 @@
+package com.android.systemui.mediaprojection.appselector
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
+
+    private val taskListProvider = TestRecentTaskListProvider()
+    private val scope = CoroutineScope(Dispatchers.Unconfined)
+    private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+
+    private val view: MediaProjectionAppSelectorView = mock()
+
+    private val controller = MediaProjectionAppSelectorController(
+        taskListProvider,
+        scope,
+        appSelectorComponentName
+    )
+
+    @Test
+    fun initNoRecentTasks_bindsEmptyList() {
+        taskListProvider.tasks = emptyList()
+
+        controller.init(view)
+
+        verify(view).bind(emptyList())
+    }
+
+    @Test
+    fun initOneRecentTask_bindsList() {
+        taskListProvider.tasks = listOf(
+            createRecentTask(taskId = 1)
+        )
+
+        controller.init(view)
+
+        verify(view).bind(
+            listOf(
+                createRecentTask(taskId = 1)
+            )
+        )
+    }
+
+    @Test
+    fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
+        val tasks = listOf(
+            createRecentTask(taskId = 1),
+            createRecentTask(taskId = 2),
+            createRecentTask(taskId = 3),
+        )
+        taskListProvider.tasks = tasks
+
+        controller.init(view)
+
+        verify(view).bind(
+            listOf(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
+            )
+        )
+    }
+
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
+        val tasks = listOf(
+            createRecentTask(taskId = 1),
+            createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
+            createRecentTask(taskId = 3),
+            createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+            createRecentTask(taskId = 5),
+        )
+        taskListProvider.tasks = tasks
+
+        controller.init(view)
+
+        verify(view).bind(
+            listOf(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 5),
+                createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+            )
+        )
+    }
+
+    private fun createRecentTask(
+        taskId: Int,
+        topActivityComponent: ComponentName? = null
+    ): RecentTask {
+        return RecentTask(
+            taskId = taskId,
+            topActivityComponent = topActivityComponent,
+            baseIntentComponent = ComponentName("com", "Test"),
+            userId = 0,
+            colorBackground = 0
+        )
+    }
+
+    private class TestRecentTaskListProvider : RecentTaskListProvider {
+
+        var tasks: List<RecentTask> = emptyList()
+
+        override suspend fun loadRecentTasks(): List<RecentTask> = tasks
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
new file mode 100644
index 0000000..939af16
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.app.ActivityManager.RecentTaskInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.google.common.truth.Truth.assertThat
+import java.util.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShellRecentTaskListProviderTest : SysuiTestCase() {
+
+    private val dispatcher = Dispatchers.Unconfined
+    private val recentTasks: RecentTasks = mock()
+    private val recentTaskListProvider =
+        ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+
+    @Test
+    fun loadRecentTasks_oneTask_returnsTheSameTask() {
+        givenRecentTasks(createSingleTask(taskId = 1))
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result).containsExactly(createRecentTask(taskId = 1))
+    }
+
+    @Test
+    fun loadRecentTasks_multipleTasks_returnsTheSameTasks() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createSingleTask(taskId = 2),
+            createSingleTask(taskId = 3),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
+            )
+    }
+
+    @Test
+    fun loadRecentTasks_groupedTask_returnsUngroupedTasks() {
+        givenRecentTasks(createTaskPair(taskId1 = 1, taskId2 = 2))
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
+    }
+
+    @Test
+    fun loadRecentTasks_mixedSingleAndGroupedTask_returnsUngroupedTasks() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3),
+            createSingleTask(taskId = 4),
+            createTaskPair(taskId1 = 5, taskId2 = 6),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 4),
+                createRecentTask(taskId = 5),
+                createRecentTask(taskId = 6),
+            )
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+        whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
+            val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+            consumer.accept(tasks.toList())
+        }
+    }
+
+    private fun createRecentTask(taskId: Int): RecentTask =
+        RecentTask(
+            taskId = taskId,
+            userId = 0,
+            topActivityComponent = null,
+            baseIntentComponent = null,
+            colorBackground = null
+        )
+
+    private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
+
+    private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
+
+    private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
+}
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 5d5918d..d2c2d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.qs;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
@@ -49,13 +52,13 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -93,7 +96,7 @@
     @Mock private QSPanel.QSTileLayout mQsTileLayout;
     @Mock private QSPanel.QSTileLayout mQQsTileLayout;
     @Mock private QSAnimator mQSAnimator;
-    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private QSSquishinessController mSquishinessController;
     private View mQsFragmentView;
 
@@ -158,7 +161,7 @@
     public void
             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
-        setStatusBarState(StatusBarState.KEYGUARD);
+        setStatusBarState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
@@ -174,7 +177,7 @@
     public void
             transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
-        setStatusBarState(StatusBarState.KEYGUARD);
+        setStatusBarState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
@@ -262,6 +265,27 @@
     }
 
     @Test
+    public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+        boolean isTransitioningToFullShade = false;
+        float transitionProgress = 0;
+        float squishinessFraction = 0f;
+
+        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+                squishinessFraction);
+
+        // trigger alpha refresh with non-zero expansion and fraction values
+        fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
+                /* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
+
+        // alpha should follow lockscreen to shade progress, not panel expansion fraction
+        assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+    }
+
+    @Test
     public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
         QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
@@ -402,6 +426,19 @@
         verify(mQSPanelController).setListening(eq(true), anyBoolean());
     }
 
+    @Test
+    public void passCorrectExpansionState_inSplitShade() {
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+        clearInvocations(mQSPanelController);
+
+        fragment.setExpanded(true);
+        verify(mQSPanelController).setExpanded(true);
+
+        fragment.setExpanded(false);
+        verify(mQSPanelController).setExpanded(false);
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index ecc8457..3cad2a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -44,7 +44,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -87,7 +86,6 @@
     @Mock
     private QSLogger mQSLogger;
     private DumpManager mDumpManager = new DumpManager();
-    private MediaCarouselController mMediaCarouselController;
     @Mock
     QSTileImpl mQSTile;
     @Mock
@@ -110,9 +108,9 @@
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-                DumpManager dumpManager, MediaCarouselController mediaCarouselController) {
+                DumpManager dumpManager) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager, mediaCarouselController);
+                    qsLogger, dumpManager);
         }
 
         @Override
@@ -146,7 +144,7 @@
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
 
         mController.init();
         reset(mQSTileRevealController);
@@ -158,7 +156,7 @@
 
         QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
                 mQSTileHost, mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController) {
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
             @Override
             protected QSTileRevealController createTileRevealController() {
                 return mQSTileRevealController;
@@ -226,7 +224,9 @@
                 + "  Tile records:\n"
                 + "    " + mockTileString + "\n"
                 + "    " + mockTileViewString + "\n"
-                + "  media bounds: null\n";
+                + "  media bounds: null\n"
+                + "  horizontal layout: false\n"
+                + "  last orientation: 0\n";
         assertEquals(expected, w.getBuffer().toString());
     }
 
@@ -251,7 +251,7 @@
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
         mController.init();
 
         assertThat(mController.shouldUseHorizontalLayout()).isTrue();
@@ -260,7 +260,7 @@
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
         mController.init();
 
         assertThat(mController.shouldUseHorizontalLayout()).isFalse();
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 98d499a..5eb9a98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.FalsingManager
@@ -41,7 +40,6 @@
     @Mock private lateinit var qsCustomizerController: QSCustomizerController
     @Mock private lateinit var qsTileRevealControllerFactory: QSTileRevealController.Factory
     @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var qsLogger: QSLogger
@@ -78,7 +76,6 @@
             mediaHost,
             qsTileRevealControllerFactory,
             dumpManager,
-            mediaCarouselController,
             metricsLogger,
             uiEventLogger,
             qsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 233c267..1c686c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -726,7 +726,7 @@
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
         when(mSecurityController.getLabel(any())).thenReturn(PARENTAL_CONTROLS_LABEL);
 
-        View view = mFooterUtils.createDialogView();
+        View view = mFooterUtils.createDialogView(getContext());
         TextView textView = (TextView) view.findViewById(R.id.parental_controls_title);
         assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText());
     }
@@ -749,7 +749,7 @@
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
-        View view = mFooterUtils.createDialogView();
+        View view = mFooterUtils.createDialogView(getContext());
 
         TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
         assertEquals(View.VISIBLE, managementSubtitle.getVisibility());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 39f27d4..6af8e49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,7 +23,6 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.qs.QSTile
@@ -60,7 +59,6 @@
     @Mock private lateinit var tileLayout: TileLayout
     @Mock private lateinit var tileView: QSTileView
     @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
-    @Mock private lateinit var mediaCarouselController: MediaCarouselController
 
     private val uiEventLogger = UiEventLoggerFake()
     private val dumpManager = DumpManager()
@@ -90,8 +88,7 @@
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
-                dumpManager,
-                mediaCarouselController)
+                dumpManager)
 
         controller.init()
     }
@@ -123,8 +120,7 @@
 
     @Test
     fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
-        // times(2) because both controller and base controller are registering their listeners
-        verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+        verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
 
         // verify that media starts in the expanded state by default
         verify(mediaHost).expansion = MediaHostState.EXPANDED
@@ -139,8 +135,7 @@
 
     @Test
     fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
-        // times(2) because both controller and base controller are registering their listeners
-        verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+        verify(quickQSPanel).addOnConfigurationChangedListener(captor.capture())
         reset(mediaHost)
 
         usingCollapsedLandscapeMedia = false
@@ -160,8 +155,7 @@
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
-        dumpManager: DumpManager,
-        mediaCarouselController: MediaCarouselController
+        dumpManager: DumpManager
     ) :
         QuickQSPanelController(
             view,
@@ -173,8 +167,7 @@
             metricsLogger,
             uiEventLogger,
             qsLogger,
-            dumpManager,
-            mediaCarouselController) {
+            dumpManager) {
 
         private var rotation = RotationUtils.ROTATION_NONE
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index eb907bd..39d89bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -110,7 +110,7 @@
         `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
         `when`(variableDateViewControllerFactory.create(any()))
                 .thenReturn(variableDateViewController)
-        `when`(iconManagerFactory.create(any())).thenReturn(iconManager)
+        `when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
         `when`(view.resources).thenReturn(mContext.resources)
         `when`(view.isAttachedToWindow).thenReturn(true)
         `when`(view.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index b86713d..451e911 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.HotspotController;
 
@@ -122,4 +123,40 @@
                 .isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network));
         mockitoSession.finishMocking();
     }
+
+    @Test
+    public void testIcon_whenDisabled_isOffState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(false);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(false);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off));
+    }
+
+    @Test
+    public void testIcon_whenTransient_isSearchState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(true);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search));
+    }
+
+    @Test
+    public void testIcon_whenEnabled_isOnState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(false);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
deleted file mode 100644
index 5a4bafc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.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.screenshot
-
-import android.app.IActivityTaskManager
-import android.graphics.Rect
-import android.hardware.display.DisplayManager
-import android.os.Binder
-import android.os.IBinder
-import android.testing.AndroidTestingRunner
-import android.view.Display
-import android.window.ScreenCapture.ScreenshotHardwareBuffer
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Test the logic within ImageCaptureImpl
- */
-@RunWith(AndroidTestingRunner::class)
-class ImageCaptureImplTest : SysuiTestCase() {
-    private val displayManager = mock<DisplayManager>()
-    private val atmService = mock<IActivityTaskManager>()
-    private val capture = TestableImageCaptureImpl(
-        displayManager,
-        atmService,
-        Dispatchers.Unconfined)
-
-    @Test
-    fun captureDisplayWithCrop() {
-        capture.captureDisplay(Display.DEFAULT_DISPLAY, Rect(1, 2, 3, 4))
-        assertThat(capture.token).isNotNull()
-        assertThat(capture.width!!).isEqualTo(2)
-        assertThat(capture.height!!).isEqualTo(2)
-        assertThat(capture.crop!!).isEqualTo(Rect(1, 2, 3, 4))
-    }
-
-    @Test
-    fun captureDisplayWithNullCrop() {
-        capture.captureDisplay(Display.DEFAULT_DISPLAY, null)
-        assertThat(capture.token).isNotNull()
-        assertThat(capture.width!!).isEqualTo(0)
-        assertThat(capture.height!!).isEqualTo(0)
-        assertThat(capture.crop!!).isEqualTo(Rect())
-    }
-
-    class TestableImageCaptureImpl(
-        displayManager: DisplayManager,
-        atmService: IActivityTaskManager,
-        bgDispatcher: CoroutineDispatcher
-    ) :
-        ImageCaptureImpl(displayManager, atmService, bgDispatcher) {
-
-        var token: IBinder? = null
-        var width: Int? = null
-        var height: Int? = null
-        var crop: Rect? = null
-
-        override fun physicalDisplayToken(displayId: Int): IBinder {
-            return Binder()
-        }
-
-        override fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect):
-                ScreenshotHardwareBuffer {
-            this.token = displayToken
-            this.width = width
-            this.height = height
-            this.crop = crop
-            return ScreenshotHardwareBuffer(
-                null,
-                null,
-                false,
-                false
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 7d56339..4c44dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -33,6 +33,7 @@
 import android.graphics.Paint;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.provider.MediaStore;
 import android.testing.AndroidTestingRunner;
 
@@ -97,7 +98,8 @@
         Bitmap original = createCheckerBitmap(10, 10, 10);
 
         ListenableFuture<ImageExporter.Result> direct =
-                exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME);
+                exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME,
+                        Process.myUserHandle());
         assertTrue("future should be done", direct.isDone());
         assertFalse("future should not be canceled", direct.isCancelled());
         ImageExporter.Result result = direct.get();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 69b7b88..8c9404e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -180,7 +180,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
@@ -208,7 +208,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
@@ -236,7 +236,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action deleteAction = task.createDeleteAction(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c448538..c76d9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -176,7 +176,7 @@
         }
         whenever(view.visibility).thenAnswer { _ -> viewVisibility }
 
-        whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+        whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
 
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
         whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 5ecfc8eb..90ae693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -97,7 +97,7 @@
         whenever(view.visibility).thenAnswer { _ -> viewVisibility }
         whenever(variableDateViewControllerFactory.create(any()))
             .thenReturn(variableDateViewController)
-        whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+        whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
         mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
                 view,
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 b40d5ac..37be343 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -20,6 +20,9 @@
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+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;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
@@ -153,7 +156,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.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -294,8 +296,8 @@
     private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
     private final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    private final PanelExpansionStateManager mPanelExpansionStateManager =
-            new PanelExpansionStateManager();
+    private final ShadeExpansionStateManager mShadeExpansionStateManager =
+            new ShadeExpansionStateManager();
     private FragmentHostManager.FragmentListener mFragmentListener;
 
     @Before
@@ -472,7 +474,7 @@
                 mLargeScreenShadeHeaderController,
                 mScreenOffAnimationController,
                 mLockscreenGestureLogger,
-                mPanelExpansionStateManager,
+                mShadeExpansionStateManager,
                 mNotificationRemoteInputManager,
                 mSysUIUnfoldComponent,
                 mInteractionJankMonitor,
@@ -1249,14 +1251,10 @@
     @Test
     public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
         enableSplitShade(/* enabled= */ true);
-        // set panel state to CLOSED
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
-                /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        mShadeExpansionStateManager.updateState(STATE_CLOSED);
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
 
-        // change panel state to OPENING
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0.5f,
-                /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 100);
+        mShadeExpansionStateManager.updateState(STATE_OPENING);
 
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
     }
@@ -1264,19 +1262,27 @@
     @Test
     public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
         enableSplitShade(/* enabled= */ true);
-        // set panel state to CLOSED
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
-                /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        mShadeExpansionStateManager.updateState(STATE_CLOSED);
 
-        // go to lockscreen, which also sets fraction to 1.0f and makes shade "expanded"
         mStatusBarStateController.setState(KEYGUARD);
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 0);
+        // going to lockscreen would trigger STATE_OPENING
+        mShadeExpansionStateManager.updateState(STATE_OPENING);
 
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
     }
 
     @Test
+    public void testQsImmediateResetsWhenPanelOpensOrCloses() {
+        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mShadeExpansionStateManager.updateState(STATE_OPEN);
+        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+
+        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mShadeExpansionStateManager.updateState(STATE_CLOSED);
+        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+    }
+
+    @Test
     public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
         // to make sure shade is in expanded state
         mNotificationPanelViewController.startWaitingForOpenPanelGesture();
@@ -1293,7 +1299,7 @@
 
     @Test
     public void testPanelClosedWhenClosingQsInSplitShade() {
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+        mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
                 /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
         enableSplitShade(/* enabled= */ true);
         mNotificationPanelViewController.setExpandedFraction(1f);
@@ -1305,7 +1311,7 @@
 
     @Test
     public void testPanelStaysOpenWhenClosingQs() {
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+        mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
                 /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
         mNotificationPanelViewController.setExpandedFraction(1f);
 
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 2adc389..db7e017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,12 +21,16 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardHostViewController
 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.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -37,7 +41,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.panelstate.PanelExpansionStateManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -51,9 +54,9 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
+@SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
-@SmallTest
 class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
     @Mock
     private lateinit var view: NotificationShadeWindowView
@@ -72,8 +75,12 @@
     @Mock
     private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
     private lateinit var ambientState: AmbientState
     @Mock
+    private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+    @Mock
     private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
     @Mock
     private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -87,6 +94,10 @@
     private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock
     private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
+    @Mock lateinit var keyguardBouncerContainer: ViewGroup
+    @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
+    @Mock lateinit var keyguardHostViewController: KeyguardHostViewController
 
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
     private lateinit var interactionEventHandler: InteractionEventHandler
@@ -97,7 +108,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(view.bottom).thenReturn(VIEW_BOTTOM)
-
         underTest = NotificationShadeWindowViewController(
             lockscreenShadeTransitionController,
             FalsingCollectorFake(),
@@ -106,7 +116,7 @@
             notificationShadeDepthController,
             view,
             notificationPanelViewController,
-            PanelExpansionStateManager(),
+            ShadeExpansionStateManager(),
             stackScrollLayoutController,
             statusBarKeyguardViewManager,
             statusBarWindowStateController,
@@ -115,7 +125,10 @@
             notificationShadeWindowController,
             keyguardUnlockAnimationController,
             ambientState,
-            pulsingGestureListener
+            pulsingGestureListener,
+            featureFlags,
+            keyguardBouncerViewModel,
+            keyguardBouncerComponentFactory
         )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 001bfee..26a0770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -33,11 +33,14 @@
 import androidx.test.filters.SmallTest;
 
 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.classifier.FalsingCollectorFake;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -48,7 +51,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.tuner.TunerService;
 
@@ -86,6 +88,9 @@
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private AmbientState mAmbientState;
     @Mock private PulsingGestureListener mPulsingGestureListener;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
+    @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
 
     @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
             mInteractionEventHandlerCaptor;
@@ -112,7 +117,7 @@
                 mNotificationShadeDepthController,
                 mView,
                 mNotificationPanelViewController,
-                new PanelExpansionStateManager(),
+                new ShadeExpansionStateManager(),
                 mNotificationStackScrollLayoutController,
                 mStatusBarKeyguardViewManager,
                 mStatusBarWindowStateController,
@@ -121,7 +126,10 @@
                 mNotificationShadeWindowController,
                 mKeyguardUnlockAnimationController,
                 mAmbientState,
-                mPulsingGestureListener
+                mPulsingGestureListener,
+                mFeatureFlags,
+                mKeyguardBouncerViewModel,
+                mKeyguardBouncerComponentFactory
         );
         mController.setupExpandedStatusBar();
         mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
new file mode 100644
index 0000000..a601b67
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.systemui.shade
+
+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
+
+@SmallTest
+class ShadeExpansionStateManagerTest : SysuiTestCase() {
+
+    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+
+    @Before
+    fun setUp() {
+        shadeExpansionStateManager = ShadeExpansionStateManager()
+    }
+
+    @Test
+    fun onPanelExpansionChanged_listenerNotified() {
+        val listener = TestShadeExpansionListener()
+        shadeExpansionStateManager.addExpansionListener(listener)
+        val fraction = 0.6f
+        val expanded = true
+        val tracking = true
+        val dragDownAmount = 1234f
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction,
+            expanded,
+            tracking,
+            dragDownAmount
+        )
+
+        assertThat(listener.fraction).isEqualTo(fraction)
+        assertThat(listener.expanded).isEqualTo(expanded)
+        assertThat(listener.tracking).isEqualTo(tracking)
+        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
+    }
+
+    @Test
+    fun addExpansionListener_listenerNotifiedOfCurrentValues() {
+        val fraction = 0.6f
+        val expanded = true
+        val tracking = true
+        val dragDownAmount = 1234f
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction,
+            expanded,
+            tracking,
+            dragDownAmount
+        )
+        val listener = TestShadeExpansionListener()
+
+        shadeExpansionStateManager.addExpansionListener(listener)
+
+        assertThat(listener.fraction).isEqualTo(fraction)
+        assertThat(listener.expanded).isEqualTo(expanded)
+        assertThat(listener.tracking).isEqualTo(tracking)
+        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
+    }
+
+    @Test
+    fun updateState_listenerNotified() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+
+        shadeExpansionStateManager.updateState(STATE_OPEN)
+
+        assertThat(listener.state).isEqualTo(STATE_OPEN)
+    }
+
+    /* ***** [PanelExpansionStateManager.onPanelExpansionChanged] test cases *******/
+
+    /* Fraction < 1 test cases */
+
+    @Test
+    fun onPEC_fractionLessThanOne_expandedTrue_trackingFalse_becomesStateOpening() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 0.5f,
+            expanded = true,
+            tracking = false,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_OPENING)
+    }
+
+    @Test
+    fun onPEC_fractionLessThanOne_expandedTrue_trackingTrue_becomesStateOpening() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 0.5f,
+            expanded = true,
+            tracking = true,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_OPENING)
+    }
+
+    @Test
+    fun onPEC_fractionLessThanOne_expandedFalse_trackingFalse_becomesStateClosed() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+        // Start out on a different state
+        shadeExpansionStateManager.updateState(STATE_OPEN)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 0.5f,
+            expanded = false,
+            tracking = false,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_CLOSED)
+    }
+
+    @Test
+    fun onPEC_fractionLessThanOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+        // Start out on a different state
+        shadeExpansionStateManager.updateState(STATE_OPEN)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 0.5f,
+            expanded = false,
+            tracking = true,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_OPEN)
+    }
+
+    /* Fraction = 1 test cases */
+
+    @Test
+    fun onPEC_fractionOne_expandedTrue_trackingFalse_becomesStateOpeningThenStateOpen() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 1f,
+            expanded = true,
+            tracking = false,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.previousState).isEqualTo(STATE_OPENING)
+        assertThat(listener.state).isEqualTo(STATE_OPEN)
+    }
+
+    @Test
+    fun onPEC_fractionOne_expandedTrue_trackingTrue_becomesStateOpening() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 1f,
+            expanded = true,
+            tracking = true,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_OPENING)
+    }
+
+    @Test
+    fun onPEC_fractionOne_expandedFalse_trackingFalse_becomesStateClosed() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+        // Start out on a different state
+        shadeExpansionStateManager.updateState(STATE_OPEN)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 1f,
+            expanded = false,
+            tracking = false,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_CLOSED)
+    }
+
+    @Test
+    fun onPEC_fractionOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
+        val listener = TestShadeStateListener()
+        shadeExpansionStateManager.addStateListener(listener)
+        // Start out on a different state
+        shadeExpansionStateManager.updateState(STATE_OPEN)
+
+        shadeExpansionStateManager.onPanelExpansionChanged(
+            fraction = 1f,
+            expanded = false,
+            tracking = true,
+            dragDownPxAmount = 0f
+        )
+
+        assertThat(listener.state).isEqualTo(STATE_OPEN)
+    }
+
+    /* ***** end [PanelExpansionStateManager.onPanelExpansionChanged] test cases ******/
+
+    class TestShadeExpansionListener : ShadeExpansionListener {
+        var fraction: Float = 0f
+        var expanded: Boolean = false
+        var tracking: Boolean = false
+        var dragDownAmountPx: Float = 0f
+
+        override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+            this.fraction = event.fraction
+            this.expanded = event.expanded
+            this.tracking = event.tracking
+            this.dragDownAmountPx = event.dragDownPxAmount
+        }
+    }
+
+    class TestShadeStateListener : ShadeStateListener {
+        @PanelState var previousState: Int = STATE_CLOSED
+        @PanelState var state: Int = STATE_CLOSED
+
+        override fun onPanelStateChanged(state: Int) {
+            this.previousState = this.state
+            this.state = state
+        }
+    }
+}
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 6be76a6..84f8656 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
@@ -5,13 +5,13 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+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.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import org.junit.Before
@@ -148,7 +148,7 @@
 
     companion object {
         val EXPANSION_EVENT =
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index b6f8326..7cac854 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -7,12 +7,12 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.STATE_OPENING
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import org.junit.Before
 import org.junit.Test
@@ -40,7 +40,7 @@
     private lateinit var controller: ShadeTransitionController
 
     private val configurationController = FakeConfigurationController()
-    private val panelExpansionStateManager = PanelExpansionStateManager()
+    private val shadeExpansionStateManager = ShadeExpansionStateManager()
 
     @Before
     fun setUp() {
@@ -49,7 +49,7 @@
         controller =
             ShadeTransitionController(
                 configurationController,
-                panelExpansionStateManager,
+                shadeExpansionStateManager,
                 dumpManager,
                 context,
                 splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
@@ -166,7 +166,7 @@
     }
 
     private fun startPanelExpansion() {
-        panelExpansionStateManager.onPanelExpansionChanged(
+        shadeExpansionStateManager.onPanelExpansionChanged(
             DEFAULT_EXPANSION_EVENT.fraction,
             DEFAULT_EXPANSION_EVENT.expanded,
             DEFAULT_EXPANSION_EVENT.tracking,
@@ -194,7 +194,7 @@
     companion object {
         private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
         private val DEFAULT_EXPANSION_EVENT =
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0.5f,
                 expanded = true,
                 tracking = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
index aafd871..0e48b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
@@ -7,11 +7,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
+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.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
-import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import org.junit.Before
 import org.junit.Test
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 131eac6..8be138a 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
@@ -113,7 +113,7 @@
         registry.isEnabled = true
 
         verify(mockPluginManager)
-            .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
+            .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
         pluginListener = captor.value
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
index 8bc438b..5fc09c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
@@ -15,6 +15,7 @@
  */
 
 package com.android.systemui.shared.navigationbar
+
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -24,15 +25,23 @@
 import androidx.concurrent.futures.DirectExecutor
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.*
-import org.mockito.junit.MockitoJUnit
+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.junit.MockitoJUnit
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -99,4 +108,39 @@
         regionSamplingHelper.stopAndDestroy()
         verify(compositionListener).unregister(any())
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testCompositionSamplingListener_has_nonEmptyRect() {
+        // simulate race condition
+        val fakeExecutor = FakeExecutor(FakeSystemClock()) // pass in as backgroundExecutor
+        val fakeSamplingCallback = mock(RegionSamplingHelper.SamplingCallback::class.java)
+
+        whenever(fakeSamplingCallback.isSamplingEnabled).thenReturn(true)
+        whenever(wrappedSurfaceControl.isValid).thenReturn(true)
+
+        regionSamplingHelper = object : RegionSamplingHelper(sampledView, fakeSamplingCallback,
+                DirectExecutor.INSTANCE, fakeExecutor, compositionListener) {
+            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                return wrappedSurfaceControl
+            }
+        }
+        regionSamplingHelper.setWindowVisible(true)
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+
+        // make sure background task is enqueued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+
+        // make sure regionSamplingHelper will have empty Rect
+        whenever(fakeSamplingCallback.getSampledRegion(any())).thenReturn(Rect(0, 0, 0, 0))
+        regionSamplingHelper.onLayoutChange(sampledView, 0, 0, 0, 0, 0, 0, 0, 0)
+
+        // resume running of background thread
+        fakeExecutor.runAllReady()
+
+        // grab Rect passed into compositionSamplingListener and make sure it's not empty
+        val argumentGrabber = argumentCaptor<Rect>()
+        verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl),
+                argumentGrabber.capture())
+        assertThat(argumentGrabber.value.isEmpty).isFalse()
+    }
+}
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 464dfe2..ec5d089 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -635,6 +635,19 @@
     }
 
     @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/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
deleted file mode 100644
index eb4cca8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-
-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;
-
-/**
- * Verifies that particular sets of dependencies don't have dependencies on others. For example,
- * code managing notifications shouldn't directly depend on CentralSurfaces, since there are
- * platforms which want to manage notifications, but don't use CentralSurfaces.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NonPhoneDependencyTest extends SysuiTestCase {
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationListContainer mListContainer;
-    @Mock private RemoteInputController.Delegate mDelegate;
-    @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
-    @Mock private OnSettingsClickListener mOnSettingsClickListener;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-               new Handler(TestableLooper.get(this).getLooper()));
-    }
-
-    @Ignore("Causes binder calls which fail")
-    @Test
-    public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
-        NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
-        NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
-        NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
-        NotificationRemoteInputManager remoteInputManager =
-                Dependency.get(NotificationRemoteInputManager.class);
-        NotificationLockscreenUserManager lockscreenUserManager =
-                Dependency.get(NotificationLockscreenUserManager.class);
-        gutsManager.setUpWithPresenter(mPresenter, mListContainer,
-                mOnSettingsClickListener);
-        notificationLogger.setUpWithContainer(mListContainer);
-        mediaManager.setUpWithPresenter(mPresenter);
-        remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
-                mDelegate);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
-
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 853d1df..bdafa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -88,6 +89,8 @@
     @Mock
     private NotificationClickNotifier mClickNotifier;
     @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
     private KeyguardManager mKeyguardManager;
     @Mock
     private DeviceProvisionedController mDeviceProvisionedController;
@@ -344,6 +347,7 @@
                     (() -> mVisibilityProvider),
                     (() -> mNotifCollection),
                     mClickNotifier,
+                    (() -> mOverviewProxyService),
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
                     Handler.createAsync(Looper.myLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 6446fb5..77b1e37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -28,10 +28,10 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.WallpaperController
@@ -137,7 +137,7 @@
     @Test
     fun onPanelExpansionChanged_apliesBlur_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
@@ -145,7 +145,7 @@
     @Test
     fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0.01f, expanded = false, tracking = false, dragDownPxAmount = 0f))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
@@ -155,7 +155,7 @@
         onPanelExpansionChanged_animatesBlurIn_ifShade()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0f, expanded = false, tracking = false, dragDownPxAmount = 0f))
         verify(shadeAnimation).animateTo(eq(0))
     }
@@ -163,7 +163,7 @@
     @Test
     fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
         val event =
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
         onPanelExpansionChanged_apliesBlur_ifShade()
         clearInvocations(shadeAnimation)
@@ -184,7 +184,7 @@
         onPanelExpansionChanged_animatesBlurOut_ifFlick()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0.6f, expanded = true, tracking = true, dragDownPxAmount = 0f))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
@@ -192,7 +192,7 @@
     @Test
     fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
         val event =
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
         notificationShadeDepthController.panelPullDownMinFraction = 0.5f
         notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -220,7 +220,7 @@
         statusBarState = StatusBarState.KEYGUARD
         notificationShadeDepthController.qsPanelExpansion = 1f
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -231,7 +231,7 @@
         statusBarState = StatusBarState.KEYGUARD
         notificationShadeDepthController.qsPanelExpansion = 0.25f
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(wallpaperController)
@@ -243,7 +243,7 @@
         enableSplitShade()
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
@@ -255,7 +255,7 @@
         disableSplitShade()
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
@@ -269,7 +269,7 @@
         val expanded = true
         val tracking = false
         val dragDownPxAmount = 0f
-        val event = PanelExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
+        val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
         val inOrder = Mockito.inOrder(wallpaperController)
 
         notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -333,7 +333,7 @@
     @Test
     fun updateBlurCallback_setsBlur_whenExpanded() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -343,7 +343,7 @@
     @Test
     fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.blursDisabledForAppLaunch = true
@@ -361,7 +361,7 @@
     @Test
     fun ignoreBlurForUnlock_ignores() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
@@ -378,7 +378,7 @@
     @Test
     fun ignoreBlurForUnlock_doesNotIgnore() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
@@ -410,7 +410,7 @@
         `when`(brightnessSpring.ratio).thenReturn(1f)
         // And shade is blurred
         notificationShadeDepthController.onPanelExpansionChanged(
-            PanelExpansionChangeEvent(
+            ShadeExpansionChangeEvent(
                 fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 9f21409..82e32b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection;
 
 import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
+import static com.android.systemui.statusbar.notification.collection.ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,6 +46,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -125,6 +127,10 @@
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
 
+    private Log.TerribleFailureHandler mOldWtfHandler = null;
+    private Log.TerribleFailure mLastWtf = null;
+    private int mWtfCount = 0;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -1748,14 +1754,17 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the filter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1767,18 +1776,24 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the filter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
 
     @Test
-    public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() {
+    public void testNonConsecutiveOutOfOrderInvalidationsDontThrowAfterTooManyRuns() {
         // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
         CountingInvalidator invalidator = new CountingInvalidator(filter);
@@ -1786,17 +1801,22 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        // WHEN we try to run the pipeline and the filter is invalidated at least
-        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        interceptWtfs();
 
-        // THEN an exception is NOT thrown.
+        // WHEN we try to run the pipeline and the filter is invalidated
+        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason,
+        // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again,
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        dispatchBuild();
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
+        dispatchBuild();
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+        // THEN an exception is NOT thrown, but WTFs ARE logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2);
     }
 
     @Test
@@ -1808,14 +1828,18 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the promoter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1827,12 +1851,18 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the promoter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
@@ -1846,14 +1876,17 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the comparator is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1865,12 +1898,14 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the comparator is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception IS thrown.
     }
@@ -1884,14 +1919,17 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1903,16 +1941,59 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
 
+    private void interceptWtfs() {
+        assertNull(mOldWtfHandler);
+
+        mLastWtf = null;
+        mWtfCount = 0;
+
+        mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> {
+            Log.e("ShadeListBuilderTest", "Observed WTF: " + e);
+            mLastWtf = e;
+            mWtfCount++;
+        });
+    }
+
+    private void expectNoWtfs() {
+        assertNull(expectWtfs(0));
+    }
+
+    private Log.TerribleFailure expectWtf() {
+        return expectWtfs(1);
+    }
+
+    private Log.TerribleFailure expectWtfs(int expectedWtfCount) {
+        assertNotNull(mOldWtfHandler);
+
+        Log.setWtfHandler(mOldWtfHandler);
+        mOldWtfHandler = null;
+
+        Log.TerribleFailure wtf = mLastWtf;
+        int wtfCount = mWtfCount;
+
+        mLastWtf = null;
+        mWtfCount = 0;
+
+        assertEquals(expectedWtfCount, wtfCount);
+        return wtf;
+    }
+
     @Test
     public void testStableOrdering() {
         mStabilityManager.setAllowEntryReordering(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 2ee3126..2970807 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -337,6 +337,40 @@
     }
 
     @Test
+    fun testOnEntryUpdated_toAlert() {
+        // GIVEN that an entry is posted that should not heads up
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // WHEN it's updated to heads up
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onEntryUpdated(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification alerts
+        finishBind(mEntry)
+        verify(mHeadsUpManager).showNotification(mEntry)
+    }
+
+    @Test
+    fun testOnEntryUpdated_toNotAlert() {
+        // GIVEN that an entry is posted that should heads up
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // WHEN it's updated to not heads up
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryUpdated(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification is never bound or shown
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
+    @Test
     fun testOnEntryRemovedRemovesHeadsUpNotification() {
         // GIVEN the current HUN is mEntry
         addHUN(mEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
new file mode 100644
index 0000000..1531f88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
@@ -0,0 +1,321 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.mock
+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.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryMonitorTest : SysuiTestCase() {
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_plainNotification() {
+        val notification = createBasicNotification().build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3316,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+        val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = 0,
+            extras = 3316,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
+        val notification =
+            createBasicNotification()
+                .setCustomContentView(
+                    RemoteViews(context.packageName, android.R.layout.list_content)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3384,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = true,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
+        val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
+        val notification =
+            createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = 444444,
+            largeIcon = 0,
+            extras = 3212,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_bigPictureStyle() {
+        val bigPicture =
+            Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
+        val bigPictureIcon =
+            Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val notification =
+            createBasicNotification()
+                .setStyle(
+                    Notification.BigPictureStyle()
+                        .bigPicture(bigPicture)
+                        .bigLargeIcon(bigPictureIcon)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 4092,
+            bigPicture = 960000,
+            extender = 0,
+            style = "BigPictureStyle",
+            styleIcon = bigPictureIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_callingStyle() {
+        val personIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+        val fakeIntent =
+            PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+        val notification =
+            createBasicNotification()
+                .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 4084,
+            bigPicture = 0,
+            extender = 0,
+            style = "CallStyle",
+            styleIcon = personIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_messagingStyle() {
+        val personIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+        val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+        val historicPersonIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
+        val historicPerson =
+            Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
+        val historicMessage =
+            Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
+
+        val notification =
+            createBasicNotification()
+                .setStyle(
+                    Notification.MessagingStyle(person)
+                        .addMessage(message)
+                        .addHistoricMessage(historicMessage)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 5024,
+            bigPicture = 0,
+            extender = 0,
+            style = "MessagingStyle",
+            styleIcon =
+                personIcon.bitmap.allocationByteCount +
+                    historicPersonIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_carExtender() {
+        val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
+        val extender = Notification.CarExtender().setLargeIcon(carIcon)
+        val notification = createBasicNotification().extend(extender).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3612,
+            bigPicture = 0,
+            extender = 556656,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_tvWearExtender() {
+        val tvExtender = Notification.TvExtender().setChannel("channel2")
+        val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
+        val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
+        val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3820,
+            bigPicture = 0,
+            extender = 388 + wearBackground.allocationByteCount,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    private fun createBasicNotification(): Notification.Builder {
+        val smallIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
+        val largeIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
+        return Notification.Builder(context)
+            .setSmallIcon(smallIcon)
+            .setLargeIcon(largeIcon)
+            .setContentTitle("This is a title")
+            .setContentText("This is content text.")
+    }
+
+    /** This will generate a nicer error message than comparing objects */
+    private fun assertNotificationObjectSizes(
+        memoryUse: NotificationMemoryUsage,
+        smallIcon: Int,
+        largeIcon: Int,
+        extras: Int,
+        bigPicture: Int,
+        extender: Int,
+        style: String?,
+        styleIcon: Int,
+        hasCustomView: Boolean
+    ) {
+        assertThat(memoryUse.packageName).isEqualTo("test_pkg")
+        assertThat(memoryUse.notificationId)
+            .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
+        assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
+        assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
+        assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
+        if (style == null) {
+            assertThat(memoryUse.objectUsage.style).isNull()
+        } else {
+            assertThat(memoryUse.objectUsage.style).isEqualTo(style)
+        }
+        assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
+        assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
+    }
+
+    private fun getUseObject(
+        singleItemUseList: List<NotificationMemoryUsage>
+    ): NotificationMemoryUsage {
+        assertThat(singleItemUseList).hasSize(1)
+        return singleItemUseList[0]
+    }
+
+    private fun createNMMWithNotifications(
+        notifications: List<Notification>
+    ): NotificationMemoryMonitor {
+        val notifPipeline: NotifPipeline = mock()
+        val notificationEntries =
+            notifications.map { n ->
+                NotificationEntryBuilder().setTag("test").setNotification(n).build()
+            }
+        whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
+        return NotificationMemoryMonitor(notifPipeline, mock())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index 7e97629..dae0aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -40,6 +40,10 @@
                 NotificationPanelLogger.toNotificationProto(visibleNotifications)));
     }
 
+    @Override
+    public void logNotificationDrag(NotificationEntry draggedNotification) {
+    }
+
     public static class CallRecord {
         public boolean isLockscreen;
         public Notifications.NotificationList list;
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 922e93d..ed2afe7 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
@@ -40,6 +40,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -63,6 +65,7 @@
     private NotificationMenuRowPlugin.MenuItem mMenuItem =
             mock(NotificationMenuRowPlugin.MenuItem.class);
     private ShadeController mShadeController = mock(ShadeController.class);
+    private NotificationPanelLogger mNotificationPanelLogger = mock(NotificationPanelLogger.class);
 
     @Before
     public void setUp() throws Exception {
@@ -82,7 +85,7 @@
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
 
         mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager,
-                mShadeController);
+                mShadeController, mNotificationPanelLogger);
     }
 
     @Test
@@ -96,6 +99,7 @@
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
         verify(mHeadsUpManager, times(1)).releaseAllImmediately();
+        verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
 
     @Test
@@ -107,6 +111,7 @@
         verify(controller).startDragAndDrop(mRow);
         verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
                 eq(false), anyFloat());
+        verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
 
     @Test
@@ -124,6 +129,7 @@
 
         // Verify that we never start the actual drag since there is no content
         verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt());
+        verify(mNotificationPanelLogger, never()).logNotificationDrag(any());
     }
 
     private ExpandableNotificationRowDragController createSpyController() {
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 6ae021b..4353036 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
@@ -55,6 +55,7 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -179,6 +180,40 @@
     }
 
     @Test
+    public void testUpdateStackHeight_qsExpansionGreaterThanZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(1f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion,
+                BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction));
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
+    public void testUpdateStackHeight_qsExpansionZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(0f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion, expansionFraction);
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
     public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
         final float dozeAmount = 0.5f;
         mAmbientState.setDozeAmount(dozeAmount);
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 f510e48..05f8760 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
@@ -116,6 +116,7 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -150,7 +151,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -413,7 +413,7 @@
                 mNotificationGutsManager,
                 notificationLogger,
                 mNotificationInterruptStateProvider,
-                new PanelExpansionStateManager(),
+                new ShadeExpansionStateManager(),
                 mKeyguardViewMediator,
                 new DisplayMetrics(),
                 mMetricsLogger,
@@ -486,7 +486,7 @@
         when(mKeyguardViewMediator.registerCentralSurfaces(
                 any(CentralSurfacesImpl.class),
                 any(NotificationPanelViewController.class),
-                any(PanelExpansionStateManager.class),
+                any(ShadeExpansionStateManager.class),
                 any(BiometricUnlockController.class),
                 any(ViewGroup.class),
                 any(KeyguardBypassController.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 9de9db1..996851e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -40,7 +40,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -73,7 +72,6 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
-    @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -101,7 +99,7 @@
         mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
                 mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
-                mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
+                () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
                 mAuthController, mNotificationIconAreaController);
@@ -132,19 +130,11 @@
         verify(mStatusBarStateController).setIsDozing(eq(false));
     }
 
-
     @Test
     public void testPulseWhileDozing_updatesScrimController() {
         mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
         mCentralSurfaces.showKeyguardImpl();
 
-        // Keep track of callback to be able to stop the pulse
-//        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-//        doAnswer(invocation -> {
-//            pulseCallback[0] = invocation.getArgument(0);
-//            return null;
-//        }).when(mDozeScrimController).pulse(any(), anyInt());
-
         // Starting a pulse should change the scrim controller to the pulsing state
         mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
             @Override
@@ -210,4 +200,17 @@
             }
         }
     }
+
+    @Test
+    public void testStopPulsing_setPendingPulseToFalse() {
+        // GIVEN a pending pulse
+        mDozeServiceHost.setPulsePending(true);
+
+        // WHEN pulsing is stopped
+        mDozeServiceHost.stopPulsing();
+
+        // THEN isPendingPulse=false, pulseOutNow is called
+        assertFalse(mDozeServiceHost.isPulsePending());
+        verify(mDozeScrimController).pulseOutNow();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index ba5f503..6ec5cf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.keyguard.CarrierTextController;
 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.battery.BatteryMeterViewController;
@@ -123,6 +124,7 @@
     private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
     @Mock private SecureSettings mSecureSettings;
     @Mock private CommandQueue mCommandQueue;
+    @Mock private KeyguardLogger mLogger;
 
     private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
     private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -135,7 +137,7 @@
 
         MockitoAnnotations.initMocks(this);
 
-        when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
 
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
@@ -172,7 +174,8 @@
                 mStatusBarUserInfoTracker,
                 mSecureSettings,
                 mCommandQueue,
-                mFakeExecutor
+                mFakeExecutor,
+                mLogger
         );
     }
 
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 de7db74..9c56c26 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
@@ -44,6 +44,7 @@
 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.viewmodel.WifiViewModel;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -51,8 +52,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import javax.inject.Provider;
-
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -79,8 +78,10 @@
         LinearLayout layout = new LinearLayout(mContext);
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
+                StatusBarLocation.HOME,
                 mock(StatusBarPipelineFlags.class),
-                () -> mock(WifiViewModel.class),
+                mock(WifiViewModel.class),
+                mock(MobileUiAdapter.class),
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -121,13 +122,17 @@
 
         TestDarkIconManager(
                 LinearLayout group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
+                    mobileUiAdapter,
                     contextProvider,
                     darkIconDispatcher);
         }
@@ -165,8 +170,10 @@
     private static class TestIconManager extends IconManager implements TestableIconManager {
         TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
             super(group,
+                    StatusBarLocation.HOME,
                     mock(StatusBarPipelineFlags.class),
-                    () -> mock(WifiViewModel.class),
+                    mock(WifiViewModel.class),
+                    mock(MobileUiAdapter.class),
                     contextProvider);
         }
 
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 a4453f8..43af112 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
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -41,21 +42,27 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -77,7 +84,7 @@
 @TestableLooper.RunWithLooper
 public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
 
-    private static final PanelExpansionChangeEvent EXPANSION_EVENT =
+    private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
             expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
 
     @Mock private ViewMediatorCallback mViewMediatorCallback;
@@ -101,6 +108,13 @@
     @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
     @Mock private DreamOverlayStateController mDreamOverlayStateController;
     @Mock private LatencyTracker mLatencyTracker;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+    @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
+    @Mock private BouncerInteractor mBouncerInteractor;
+    @Mock private BouncerView mBouncerView;
+//    @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
+    @Mock private BouncerViewDelegate mBouncerViewDelegate;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
@@ -115,6 +129,8 @@
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
                 .thenReturn(mKeyguardMessageAreaController);
+        when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
         mStatusBarKeyguardViewManager =
                 new StatusBarKeyguardViewManager(
                         getContext(),
@@ -133,11 +149,16 @@
                         mKeyguardMessageAreaFactory,
                         Optional.of(mSysUiUnfoldComponent),
                         () -> mShadeController,
-                        mLatencyTracker);
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mBouncerCallbackInteractor,
+                        mBouncerInteractor,
+                        mBouncerView);
         mStatusBarKeyguardViewManager.registerCentralSurfaces(
                 mCentralSurfaces,
                 mNotificationPanelView,
-                new PanelExpansionStateManager(),
+                new ShadeExpansionStateManager(),
                 mBiometricUnlockController,
                 mNotificationContainer,
                 mBypassController);
@@ -481,9 +502,9 @@
         Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
     }
 
-    private static PanelExpansionChangeEvent expansionEvent(
+    private static ShadeExpansionChangeEvent expansionEvent(
             float fraction, boolean expanded, boolean tracking) {
-        return new PanelExpansionChangeEvent(
+        return new ShadeExpansionChangeEvent(
                 fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
     }
 
@@ -507,20 +528,25 @@
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
     }
 
+
     @Test
-    public void testSetDozing_Dozing() {
+    public void testSetDozing_bouncerShowing_Dozing() {
         clearInvocations(mBouncer);
+        when(mBouncer.isShowing()).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mBouncer.isShowing()).thenReturn(false);
+            return null;
+        }).when(mBouncer).hide(false);
         mStatusBarKeyguardViewManager.onDozingChanged(true);
-        // Once when shown and once with dozing changed.
         verify(mBouncer, times(1)).hide(false);
     }
 
     @Test
-    public void testSetDozing_notDozing() {
+    public void testSetDozing_bouncerShowing_notDozing() {
         mStatusBarKeyguardViewManager.onDozingChanged(true);
+        when(mBouncer.isShowing()).thenReturn(true);
         clearInvocations(mBouncer);
         mStatusBarKeyguardViewManager.onDozingChanged(false);
-        // Once when shown and twice with dozing changed.
         verify(mBouncer, times(1)).hide(false);
     }
 }
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 faadd24..3a006ad 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
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
@@ -65,7 +66,6 @@
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -428,7 +428,7 @@
         mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
-        when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
         mSecureSettings = mock(SecureSettings.class);
 
         setUpNotificationIconAreaController();
@@ -438,7 +438,7 @@
                 mAnimationScheduler,
                 mLocationPublisher,
                 mMockNotificationAreaController,
-                new PanelExpansionStateManager(),
+                new ShadeExpansionStateManager(),
                 mock(FeatureFlags.class),
                 mStatusBarIconController,
                 mIconManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
deleted file mode 100644
index c4f8049..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
+++ /dev/null
@@ -1,209 +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.systemui.statusbar.phone.panelstate
-
-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
-
-@SmallTest
-class PanelExpansionStateManagerTest : SysuiTestCase() {
-
-    private lateinit var panelExpansionStateManager: PanelExpansionStateManager
-
-    @Before
-    fun setUp() {
-        panelExpansionStateManager = PanelExpansionStateManager()
-    }
-
-    @Test
-    fun onPanelExpansionChanged_listenerNotified() {
-        val listener = TestPanelExpansionListener()
-        panelExpansionStateManager.addExpansionListener(listener)
-        val fraction = 0.6f
-        val expanded = true
-        val tracking = true
-        val dragDownAmount = 1234f
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction, expanded, tracking, dragDownAmount)
-
-        assertThat(listener.fraction).isEqualTo(fraction)
-        assertThat(listener.expanded).isEqualTo(expanded)
-        assertThat(listener.tracking).isEqualTo(tracking)
-        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
-    }
-
-    @Test
-    fun addExpansionListener_listenerNotifiedOfCurrentValues() {
-        val fraction = 0.6f
-        val expanded = true
-        val tracking = true
-        val dragDownAmount = 1234f
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction, expanded, tracking, dragDownAmount)
-        val listener = TestPanelExpansionListener()
-
-        panelExpansionStateManager.addExpansionListener(listener)
-
-        assertThat(listener.fraction).isEqualTo(fraction)
-        assertThat(listener.expanded).isEqualTo(expanded)
-        assertThat(listener.tracking).isEqualTo(tracking)
-        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
-    }
-
-    @Test
-    fun updateState_listenerNotified() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-
-        panelExpansionStateManager.updateState(STATE_OPEN)
-
-        assertThat(listener.state).isEqualTo(STATE_OPEN)
-    }
-
-    /* ***** [PanelExpansionStateManager.onPanelExpansionChanged] test cases *******/
-
-    /* Fraction < 1 test cases */
-
-    @Test
-    fun onPEC_fractionLessThanOne_expandedTrue_trackingFalse_becomesStateOpening() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 0.5f, expanded = true, tracking = false, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_OPENING)
-    }
-
-    @Test
-    fun onPEC_fractionLessThanOne_expandedTrue_trackingTrue_becomesStateOpening() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_OPENING)
-    }
-
-    @Test
-    fun onPEC_fractionLessThanOne_expandedFalse_trackingFalse_becomesStateClosed() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-        // Start out on a different state
-        panelExpansionStateManager.updateState(STATE_OPEN)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 0.5f, expanded = false, tracking = false, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_CLOSED)
-    }
-
-    @Test
-    fun onPEC_fractionLessThanOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-        // Start out on a different state
-        panelExpansionStateManager.updateState(STATE_OPEN)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 0.5f, expanded = false, tracking = true, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_OPEN)
-    }
-
-    /* Fraction = 1 test cases */
-
-    @Test
-    fun onPEC_fractionOne_expandedTrue_trackingFalse_becomesStateOpeningThenStateOpen() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
-
-        assertThat(listener.previousState).isEqualTo(STATE_OPENING)
-        assertThat(listener.state).isEqualTo(STATE_OPEN)
-    }
-
-    @Test
-    fun onPEC_fractionOne_expandedTrue_trackingTrue_becomesStateOpening() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 1f, expanded = true, tracking = true, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_OPENING)
-    }
-
-    @Test
-    fun onPEC_fractionOne_expandedFalse_trackingFalse_becomesStateClosed() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-        // Start out on a different state
-        panelExpansionStateManager.updateState(STATE_OPEN)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 1f, expanded = false, tracking = false, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_CLOSED)
-    }
-
-    @Test
-    fun onPEC_fractionOne_expandedFalse_trackingTrue_doesNotBecomeStateClosed() {
-        val listener = TestPanelStateListener()
-        panelExpansionStateManager.addStateListener(listener)
-        // Start out on a different state
-        panelExpansionStateManager.updateState(STATE_OPEN)
-
-        panelExpansionStateManager.onPanelExpansionChanged(
-            fraction = 1f, expanded = false, tracking = true, dragDownPxAmount = 0f)
-
-        assertThat(listener.state).isEqualTo(STATE_OPEN)
-    }
-
-    /* ***** end [PanelExpansionStateManager.onPanelExpansionChanged] test cases ******/
-
-    class TestPanelExpansionListener : PanelExpansionListener {
-        var fraction: Float = 0f
-        var expanded: Boolean = false
-        var tracking: Boolean = false
-        var dragDownAmountPx: Float = 0f
-
-        override fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
-            this.fraction = event.fraction
-            this.expanded = event.expanded
-            this.tracking = event.tracking
-            this.dragDownAmountPx = event.dragDownPxAmount
-        }
-    }
-
-    class TestPanelStateListener : PanelStateListener {
-        @PanelState var previousState: Int = STATE_CLOSED
-        @PanelState var state: Int = STATE_CLOSED
-
-        override fun onPanelStateChanged(state: Int) {
-            this.previousState = this.state
-            this.state = state
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
index 37c0f36..bf43238 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
@@ -34,14 +34,14 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 @SmallTest
-class StatusBarUserSwitcherControllerTest : SysuiTestCase() {
+class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
     @Mock
     private lateinit var tracker: StatusBarUserInfoTracker
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 0000000..0d15268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -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 com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+    private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+    override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+    private val _activeMobileDataSubscriptionId =
+        MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+    override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+    private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+    override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+        return subIdFlows[subId]
+            ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+    }
+
+    fun setSubscriptions(subs: List<SubscriptionInfo>) {
+        _subscriptionsFlow.value = subs
+    }
+
+    fun setActiveMobileDataSubscriptionId(subId: Int) {
+        _activeMobileDataSubscriptionId.value = subId
+    }
+
+    fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+        val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+        subscription.value = model
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..6c495c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+    private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+    fun setUserSetup(setup: Boolean) {
+        _isUserSetup.value = setup
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 0000000..316b795
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+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.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+    @Mock private lateinit var subscriptionManager: SubscriptionManager
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            MobileSubscriptionRepositoryImpl(
+                subscriptionManager,
+                telephonyManager,
+                IMMEDIATE,
+                scope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testSubscriptions_initiallyEmpty() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+        }
+
+    @Test
+    fun testSubscriptions_listUpdates() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_removingSub_updatesList() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            // WHEN 2 networks show up
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // WHEN one network is removed
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // THEN the subscriptions list represents the newest change
+            assertThat(latest).isEqualTo(listOf(SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.activeMobileDataSubscriptionId.value)
+                .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_updates() =
+        runBlocking(IMMEDIATE) {
+            var active: Int? = null
+
+            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+            getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(active).isEqualTo(SUB_2_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_default() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+
+            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly_toggles() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<ServiceStateListener>()
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+            callback.onServiceStateChanged(serviceState)
+            serviceState.isEmergencyOnly = false
+            callback.onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_signalStrengths_levelsUpdate() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+            val strength = signalStrength(1, 2, true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest?.isGsm).isEqualTo(true)
+            assertThat(latest?.primaryLevel).isEqualTo(1)
+            assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataActivity() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DataActivityListener>()
+            callback.onDataActivity(3)
+
+            assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_carrierNetworkChange() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+            callback.onCarrierNetworkChange(true)
+
+            assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_displayInfo() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val ti = mock<TelephonyDisplayInfo>()
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest?.displayInfo).isEqualTo(ti)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_isCached() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            val state1 = underTest.getFlowForSubId(SUB_1_ID)
+            val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+            assertThat(state1).isEqualTo(state2)
+        }
+
+    @Test
+    fun testFlowForSubId_isRemovedAfterFinish() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+
+            // Start collecting on some flow
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            // There should be once cached flow now
+            assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+            // When the job is canceled, the cache should be cleared
+            job.cancel()
+
+            assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+        }
+
+    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+        val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+        verify(subscriptionManager)
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+        getTelephonyCallbackForType()
+
+    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+        val callbackCaptor = argumentCaptor<TelephonyCallback>()
+        verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.allValues
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+        assertThat(cbs.size).isEqualTo(1)
+        return cbs[0]
+    }
+
+    /** Convenience constructor for SignalStrength */
+    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+        val signalStrength = mock<SignalStrength>()
+        whenever(signalStrength.isGsm).thenReturn(isGsm)
+        whenever(signalStrength.level).thenReturn(gsmLevel)
+        val cdmaStrength =
+            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+            .thenReturn(listOf(cdmaStrength))
+
+        return signalStrength
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..91c233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: UserSetupRepository
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            UserSetupRepositoryImpl(
+                deviceProvisionedController,
+                IMMEDIATE,
+                scope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testUserSetup_defaultFalse() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+
+            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun testUserSetup_updatesOnChange() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+
+            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+            whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+            val callback = getDeviceProvisionedListener()
+            callback.onUserSetupChanged()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+        val captor = argumentCaptor<DeviceProvisionedListener>()
+        verify(deviceProvisionedController).addCallback(captor.capture())
+        return captor.value!!
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..8ec68f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+    private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+    override val iconGroup = _iconGroup
+
+    private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+    override val isEmergencyOnly = _isEmergencyOnly
+
+    private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+    override val level = _level
+
+    private val _numberOfLevels = MutableStateFlow<Int>(4)
+    override val numberOfLevels = _numberOfLevels
+
+    private val _cutOut = MutableStateFlow<Boolean>(false)
+    override val cutOut = _cutOut
+
+    fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+        _iconGroup.value = group
+    }
+
+    fun setIsEmergencyOnly(emergency: Boolean) {
+        _isEmergencyOnly.value = emergency
+    }
+
+    fun setLevel(level: Int) {
+        _level.value = level
+    }
+
+    fun setNumberOfLevels(num: Int) {
+        _numberOfLevels.value = num
+    }
+
+    fun setCutOut(cutOut: Boolean) {
+        _cutOut.value = cutOut
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
new file mode 100644
index 0000000..2f07d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconInteractor
+    private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+    private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+    @Before
+    fun setUp() {
+        underTest = MobileIconInteractorImpl(sub1Flow)
+    }
+
+    @Test
+    fun gsm_level_default_unknown() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(isGsm = true),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+            job.cancel()
+        }
+
+    @Test
+    fun gsm_usesGsmLevel() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(
+                    isGsm = true,
+                    primaryLevel = GSM_LEVEL,
+                    cdmaLevel = CDMA_LEVEL
+                ),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(GSM_LEVEL)
+
+            job.cancel()
+        }
+
+    @Test
+    fun cdma_level_default_unknown() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(isGsm = false),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+            job.cancel()
+        }
+
+    @Test
+    fun cdma_usesCdmaLevel() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(
+                    isGsm = false,
+                    primaryLevel = GSM_LEVEL,
+                    cdmaLevel = CDMA_LEVEL
+                ),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+
+        private const val GSM_LEVEL = 1
+        private const val CDMA_LEVEL = 2
+
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 0000000..89ad9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconsInteractor
+    private val userSetupRepository = FakeUserSetupRepository()
+    private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+    @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            MobileIconsInteractor(
+                subscriptionsRepository,
+                carrierConfigTracker,
+                userSetupRepository,
+            )
+    }
+
+    @After fun tearDown() {}
+
+    @Test
+    fun filteredSubscriptions_default() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the active one when the config is false
+            assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the active one when the config is false
+            assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(true)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+            // true
+            assertThat(latest).isEqualTo(listOf(SUB_1))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(true)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+            // true
+            assertThat(latest).isEqualTo(listOf(SUB_1))
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+        private const val SUB_3_ID = 3
+        private val SUB_3_OPP =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+                whenever(it.isOpportunistic).thenReturn(true)
+            }
+
+        private const val SUB_4_ID = 4
+        private val SUB_4_OPP =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+                whenever(it.isOpportunistic).thenReturn(true)
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 0000000..b374abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconViewModel
+    private val interactor = FakeMobileIconInteractor()
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        interactor.apply {
+            setLevel(1)
+            setCutOut(false)
+            setIconGroup(TelephonyIcons.THREE_G)
+            setIsEmergencyOnly(false)
+            setNumberOfLevels(4)
+        }
+        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+    }
+
+    @Test
+    fun iconId_correctLevel_notCutout() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private const val SUB_1_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 36be1be..0e75c74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -23,9 +23,16 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
@@ -64,12 +71,70 @@
         assertThat(actualString).contains(expectedNetId)
     }
 
-    private val NET_1_ID = 100
-    private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
-        Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+    @Test
+    fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+        val flow: Flow<Int?> = flowOf(1, null, 3)
+
+        val job = flow
+            .logOutputChange(logger, "testInts")
+            .launchIn(this)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains("1")
+        assertThat(actualString).contains("null")
+        assertThat(actualString).contains("3")
+
+        job.cancel()
     }
-    private val NET_1_CAPS = NetworkCapabilities.Builder()
-        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-        .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
-        .build()
+
+    @Test
+    fun logInputChange_unit_printsInputName() = runBlocking(IMMEDIATE) {
+        val flow: Flow<Unit> = flowOf(Unit, Unit)
+
+        val job = flow
+            .logInputChange(logger, "testInputs")
+            .launchIn(this)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains("testInputs")
+
+        job.cancel()
+    }
+
+    @Test
+    fun logInputChange_any_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+        val flow: Flow<Any?> = flowOf(null, 2, "threeString")
+
+        val job = flow
+            .logInputChange(logger, "testInputs")
+            .launchIn(this)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains("null")
+        assertThat(actualString).contains("2")
+        assertThat(actualString).contains("threeString")
+
+        job.cancel()
+    }
+
+    companion object {
+        private const val NET_1_ID = 100
+        private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+            Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+        }
+        private val NET_1_CAPS = NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+            .build()
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 6b8d4aa..f751afc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -16,20 +16,27 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 
 /** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
 class FakeWifiRepository : WifiRepository {
+    private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
     private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
         MutableStateFlow(WifiNetworkModel.Inactive)
-    override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
+    override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
-    override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
+    override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
+
+    fun setIsWifiEnabled(enabled: Boolean) {
+        _isWifiEnabled.value = enabled
+    }
 
     fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
         _wifiNetwork.value = wifiNetworkModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index d070ba0..0ba0bd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -28,15 +28,17 @@
 import android.net.wifi.WifiManager.TrafficStateCallback
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import com.android.systemui.util.concurrency.FakeExecutor
 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
@@ -44,23 +46,28 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
 import org.junit.After
 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
 import org.mockito.MockitoAnnotations
 
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class WifiRepositoryImplTest : SysuiTestCase() {
 
     private lateinit var underTest: WifiRepositoryImpl
 
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
@@ -70,16 +77,17 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(
+            broadcastDispatcher.broadcastFlow(
+                any(),
+                nullable(),
+                anyInt(),
+                nullable(),
+            )
+        ).thenReturn(flowOf(Unit))
         executor = FakeExecutor(FakeSystemClock())
         scope = CoroutineScope(IMMEDIATE)
-
-        underTest = WifiRepositoryImpl(
-            connectivityManager,
-            logger,
-            executor,
-            scope,
-            wifiManager,
-        )
+        underTest = createRepo()
     }
 
     @After
@@ -88,6 +96,132 @@
     }
 
     @Test
+    fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
+        underTest = createRepo(wifiManagerToUse = null)
+
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+    }
+
+    @Test
+    fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
+        whenever(wifiManager.isWifiEnabled).thenReturn(true)
+
+        underTest = createRepo()
+
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+    }
+
+    @Test
+    fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
+        // We need to call launch on the flows so that they start updating
+        val networkJob = underTest.wifiNetwork.launchIn(this)
+        val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(true)
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+        )
+
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(false)
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+        )
+
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+
+        networkJob.cancel()
+        enabledJob.cancel()
+    }
+
+    @Test
+    fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
+        // We need to call launch on the flows so that they start updating
+        val networkJob = underTest.wifiNetwork.launchIn(this)
+        val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(true)
+        getNetworkCallback().onLost(NETWORK)
+
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(false)
+        getNetworkCallback().onLost(NETWORK)
+
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+
+        networkJob.cancel()
+        enabledJob.cancel()
+    }
+
+    @Test
+    fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
+        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)
+
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(false)
+        intentFlow.emit(Unit)
+
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
+        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)
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(true)
+        getNetworkCallback().onLost(NETWORK)
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(false)
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+        )
+        assertThat(underTest.isWifiEnabled.value).isFalse()
+
+        whenever(wifiManager.isWifiEnabled).thenReturn(true)
+        intentFlow.emit(Unit)
+        assertThat(underTest.isWifiEnabled.value).isTrue()
+
+        networkJob.cancel()
+        enabledJob.cancel()
+    }
+
+    @Test
     fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
         val job = underTest
@@ -509,13 +643,7 @@
 
     @Test
     fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
-        underTest = WifiRepositoryImpl(
-            connectivityManager,
-            logger,
-            executor,
-            scope,
-            wifiManager = null,
-        )
+        underTest = createRepo(wifiManagerToUse = null)
 
         var latest: WifiActivityModel? = null
         val job = underTest
@@ -594,6 +722,17 @@
         job.cancel()
     }
 
+    private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
+        return WifiRepositoryImpl(
+            broadcastDispatcher,
+            connectivityManager,
+            logger,
+            executor,
+            scope,
+            wifiManagerToUse,
+        )
+    }
+
     private fun getTrafficStateCallback(): TrafficStateCallback {
         val callbackCaptor = argumentCaptor<TrafficStateCallback>()
         verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index e896749..39b886a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
 
+import android.net.wifi.WifiManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,172 +51,129 @@
     }
 
     @Test
-    fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
-        )
-
-        var latest: Boolean? = null
-        val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isFalse()
-
-        job.cancel()
-    }
-
-    @Test
-    fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
-        )
-
-        var latest: Boolean? = null
-        val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isFalse()
-
-        job.cancel()
-    }
-
-    @Test
-    fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
-        )
-
-        var latest: Boolean? = null
-        val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isTrue()
-
-        job.cancel()
-    }
-
-    @Test
-    fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        )
-
-        var latest: Boolean? = null
-        val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isTrue()
-
-        job.cancel()
-    }
-
-    @Test
-    fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        )
-
-        var latest: Boolean? = null
-        val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isFalse()
-
-        job.cancel()
-    }
-
-    @Test
-    fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
+    fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
         wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        )
 
-        var latest: Boolean? = null
+        var latest: String? = "default"
         val job = underTest
-            .hasActivityIn
+            .ssid
             .onEach { latest = it }
             .launchIn(this)
 
-        assertThat(latest).isFalse()
+        assertThat(latest).isNull()
 
         job.cancel()
     }
 
     @Test
-    fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
+    fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
         wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        )
 
-        var latest: Boolean? = null
+        var latest: String? = "default"
         val job = underTest
-            .hasActivityIn
+            .ssid
             .onEach { latest = it }
             .launchIn(this)
 
-        assertThat(latest).isFalse()
+        assertThat(latest).isNull()
 
         job.cancel()
     }
 
     @Test
-    fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+    fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            isPasspointAccessPoint = true,
+            passpointProviderFriendlyName = "friendly",
+        ))
 
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("friendly")
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            isOnlineSignUpForPasspointAccessPoint = true,
+            passpointProviderFriendlyName = "friendly",
+        ))
+
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("friendly")
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            ssid = WifiManager.UNKNOWN_SSID,
+        ))
+
+        var latest: String? = "default"
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            ssid = "MyAwesomeWifiNetwork",
+        ))
+
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
+
+        job.cancel()
+    }
+
+    @Test
+    fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
         var latest: Boolean? = null
         val job = underTest
-                .hasActivityIn
-                .onEach { latest = it }
-                .launchIn(this)
+            .isEnabled
+            .onEach { latest = it }
+            .launchIn(this)
 
-        // Conduct a series of changes and verify we catch each of them in succession
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
-        )
+        wifiRepository.setIsWifiEnabled(true)
         yield()
         assertThat(latest).isTrue()
 
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
-        )
+        wifiRepository.setIsWifiEnabled(false)
         yield()
         assertThat(latest).isFalse()
 
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        )
+        wifiRepository.setIsWifiEnabled(true)
         yield()
         assertThat(latest).isTrue()
 
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
-        )
-        yield()
-        assertThat(latest).isTrue()
-
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
-        )
-        yield()
-        assertThat(latest).isFalse()
-
         job.cancel()
     }
 
@@ -242,6 +200,32 @@
     }
 
     @Test
+    fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+            .activity
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity1)
+        yield()
+        assertThat(latest).isEqualTo(activity1)
+
+        val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity2)
+        yield()
+        assertThat(latest).isEqualTo(activity2)
+
+        val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity3)
+        yield()
+        assertThat(latest).isEqualTo(activity3)
+
+        job.cancel()
+    }
+
+    @Test
     fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
         connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
 
@@ -270,10 +254,6 @@
 
         job.cancel()
     }
-
-    companion object {
-        val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
-    }
 }
 
 private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3c200a5..4efb135 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,38 +16,225 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.view
 
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
-import com.android.systemui.util.Assert
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 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.Mock
+import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
-@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
 class ModernStatusBarWifiViewTest : SysuiTestCase() {
 
+    private lateinit var testableLooper: TestableLooper
+
+    @Mock
+    private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+    @Mock
+    private lateinit var logger: ConnectivityPipelineLogger
+    @Mock
+    private lateinit var connectivityConstants: ConnectivityConstants
+    @Mock
+    private lateinit var wifiConstants: WifiConstants
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var wifiRepository: FakeWifiRepository
+    private lateinit var interactor: WifiInteractor
+    private lateinit var viewModel: WifiViewModel
+    private lateinit var scope: CoroutineScope
+
     @JvmField @Rule
     val instantTaskExecutor = InstantTaskExecutorRule()
 
     @Before
     fun setUp() {
-        Assert.setTestThread(Thread.currentThread())
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        connectivityRepository = FakeConnectivityRepository()
+        wifiRepository = FakeWifiRepository()
+        wifiRepository.setIsWifiEnabled(true)
+        interactor = WifiInteractor(connectivityRepository, wifiRepository)
+        scope = CoroutineScope(Dispatchers.Unconfined)
+        viewModel = WifiViewModel(
+            connectivityConstants,
+            context,
+            logger,
+            interactor,
+            scope,
+            statusBarPipelineFlags,
+            wifiConstants,
+        )
     }
 
     @Test
     fun constructAndBind_hasCorrectSlot() {
         val view = ModernStatusBarWifiView.constructAndBind(
-            context, "slotName", mock()
+            context, "slotName", viewModel, StatusBarLocation.HOME
         )
 
         assertThat(view.slot).isEqualTo("slotName")
     }
+
+    @Test
+    fun getVisibleState_icon_returnsIcon() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(STATE_ICON)
+    }
+
+    @Test
+    fun getVisibleState_dot_returnsDot() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(STATE_DOT)
+    }
+
+    @Test
+    fun getVisibleState_hidden_returnsHidden() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
+    }
+
+    // Note: The following tests are more like integration tests, since they stand up a full
+    // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+
+    @Test
+    fun setVisibleState_icon_iconShownDotHidden() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun setVisibleState_dot_iconHiddenDotShown() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+        assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun setVisibleState_hidden_iconAndDotHidden() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+        assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun isIconVisible_notEnabled_outputsFalse() {
+        wifiRepository.setIsWifiEnabled(false)
+        wifiRepository.setWifiNetwork(
+            WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+        )
+
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.isIconVisible).isFalse()
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun isIconVisible_enabled_outputsTrue() {
+        wifiRepository.setIsWifiEnabled(true)
+        wifiRepository.setWifiNetwork(
+            WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+        )
+
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+        )
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.isIconVisible).isTrue()
+
+        ViewUtils.detachView(view)
+    }
+
+    private fun View.getIconGroupView(): View {
+        return this.requireViewById(R.id.wifi_group)
+    }
+
+    private fun View.getDotView(): View {
+        return this.requireViewById(R.id.status_bar_dot)
+    }
 }
+
+private const val SLOT_NAME = "TestSlotName"
+private const val NETWORK_ID = 200
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
new file mode 100644
index 0000000..929e529
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.pipeline.wifi.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase) :
+    SysuiTestCase() {
+
+    private lateinit var underTest: WifiViewModel
+
+    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var connectivityConstants: ConnectivityConstants
+    @Mock private lateinit var wifiConstants: WifiConstants
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var wifiRepository: FakeWifiRepository
+    private lateinit var interactor: WifiInteractor
+    private lateinit var scope: CoroutineScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        connectivityRepository = FakeConnectivityRepository()
+        wifiRepository = FakeWifiRepository()
+        wifiRepository.setIsWifiEnabled(true)
+        interactor = WifiInteractor(connectivityRepository, wifiRepository)
+        scope = CoroutineScope(IMMEDIATE)
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun wifiIcon() =
+        runBlocking(IMMEDIATE) {
+            wifiRepository.setIsWifiEnabled(testCase.enabled)
+            connectivityRepository.setForceHiddenIcons(
+                if (testCase.forceHidden) {
+                    setOf(ConnectivitySlot.WIFI)
+                } else {
+                    setOf()
+                }
+            )
+            whenever(wifiConstants.alwaysShowIconIfEnabled)
+                .thenReturn(testCase.alwaysShowIconWhenEnabled)
+            whenever(connectivityConstants.hasDataCapabilities)
+                .thenReturn(testCase.hasDataCapabilities)
+            underTest =
+                WifiViewModel(
+                    connectivityConstants,
+                    context,
+                    logger,
+                    interactor,
+                    scope,
+                    statusBarPipelineFlags,
+                    wifiConstants,
+                )
+
+            val iconFlow = underTest.home.wifiIcon
+            val job = iconFlow.launchIn(this)
+
+            // WHEN we set a certain network
+            wifiRepository.setWifiNetwork(testCase.network)
+            yield()
+
+            // THEN we get the expected icon
+            assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
+            val expectedContentDescription =
+                if (testCase.expected == null) {
+                    null
+                } else {
+                    testCase.expected.contentDescription.invoke(context)
+                }
+            assertThat(iconFlow.value?.contentDescription?.getAsString())
+                .isEqualTo(expectedContentDescription)
+
+            job.cancel()
+        }
+
+    private fun ContentDescription.getAsString(): String? {
+        return when (this) {
+            is ContentDescription.Loaded -> this.description
+            is ContentDescription.Resource -> context.getString(this.res)
+        }
+    }
+
+    internal data class Expected(
+        /** The resource that should be used for the icon. */
+        @DrawableRes val iconResource: Int,
+
+        /** A function that, given a context, calculates the correct content description string. */
+        val contentDescription: (Context) -> String,
+    )
+
+    // Note: We use default values for the boolean parameters to reflect a "typical configuration"
+    //   for wifi. This allows each TestCase to only define the parameter values that are critical
+    //   for the test function.
+    internal data class TestCase(
+        val enabled: Boolean = true,
+        val forceHidden: Boolean = false,
+        val alwaysShowIconWhenEnabled: Boolean = false,
+        val hasDataCapabilities: Boolean = true,
+        val network: WifiNetworkModel,
+
+        /** The expected output. Null if we expect the output to be null. */
+        val expected: Expected?
+    )
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): Collection<TestCase> =
+            listOf(
+                // Enabled = false => no networks shown
+                TestCase(
+                    enabled = false,
+                    network = WifiNetworkModel.CarrierMerged,
+                    expected = null,
+                ),
+                TestCase(
+                    enabled = false,
+                    network = WifiNetworkModel.Inactive,
+                    expected = null,
+                ),
+                TestCase(
+                    enabled = false,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 1),
+                    expected = null,
+                ),
+                TestCase(
+                    enabled = false,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 3),
+                    expected = null,
+                ),
+
+                // forceHidden = true => no networks shown
+                TestCase(
+                    forceHidden = true,
+                    network = WifiNetworkModel.CarrierMerged,
+                    expected = null,
+                ),
+                TestCase(
+                    forceHidden = true,
+                    network = WifiNetworkModel.Inactive,
+                    expected = null,
+                ),
+                TestCase(
+                    enabled = false,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2),
+                    expected = null,
+                ),
+                TestCase(
+                    forceHidden = true,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1),
+                    expected = null,
+                ),
+
+                // alwaysShowIconWhenEnabled = true => all Inactive and Active networks shown
+                TestCase(
+                    alwaysShowIconWhenEnabled = true,
+                    network = WifiNetworkModel.Inactive,
+                    expected =
+                        Expected(
+                            iconResource = WifiIcons.WIFI_NO_NETWORK,
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_NO_CONNECTION)}," +
+                                    context.getString(NO_INTERNET)
+                            }
+                        ),
+                ),
+                TestCase(
+                    alwaysShowIconWhenEnabled = true,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_NO_INTERNET_ICONS[4],
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_CONNECTION_STRENGTH[4])}," +
+                                    context.getString(NO_INTERNET)
+                            }
+                        ),
+                ),
+                TestCase(
+                    alwaysShowIconWhenEnabled = true,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_FULL_ICONS[2],
+                            contentDescription = { context ->
+                                context.getString(WIFI_CONNECTION_STRENGTH[2])
+                            }
+                        ),
+                ),
+
+                // hasDataCapabilities = false => all Inactive and Active networks shown
+                TestCase(
+                    hasDataCapabilities = false,
+                    network = WifiNetworkModel.Inactive,
+                    expected =
+                        Expected(
+                            iconResource = WifiIcons.WIFI_NO_NETWORK,
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_NO_CONNECTION)}," +
+                                    context.getString(NO_INTERNET)
+                            }
+                        ),
+                ),
+                TestCase(
+                    hasDataCapabilities = false,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_NO_INTERNET_ICONS[2],
+                            contentDescription = { context ->
+                                "${context.getString(WIFI_CONNECTION_STRENGTH[2])}," +
+                                    context.getString(NO_INTERNET)
+                            }
+                        ),
+                ),
+                TestCase(
+                    hasDataCapabilities = false,
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 0),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_FULL_ICONS[0],
+                            contentDescription = { context ->
+                                context.getString(WIFI_CONNECTION_STRENGTH[0])
+                            }
+                        ),
+                ),
+
+                // network = CarrierMerged => not shown
+                TestCase(
+                    network = WifiNetworkModel.CarrierMerged,
+                    expected = null,
+                ),
+
+                // network = Inactive => not shown
+                TestCase(
+                    network = WifiNetworkModel.Inactive,
+                    expected = null,
+                ),
+
+                // network = Active & validated = false => not shown
+                TestCase(
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
+                    expected = null,
+                ),
+
+                // network = Active & validated = true => shown
+                TestCase(
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 4),
+                    expected =
+                        Expected(
+                            iconResource = WIFI_FULL_ICONS[4],
+                            contentDescription = { context ->
+                                context.getString(WIFI_CONNECTION_STRENGTH[4])
+                            }
+                        ),
+                ),
+
+                // network has null level => not shown
+                TestCase(
+                    network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = null),
+                    expected = null,
+                ),
+            )
+    }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
+private const val NETWORK_ID = 789
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 43103a0..3169eef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -17,37 +17,34 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.yield
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class WifiViewModelTest : SysuiTestCase() {
@@ -56,236 +53,426 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
-    @Mock private lateinit var constants: WifiConstants
+    @Mock private lateinit var connectivityConstants: ConnectivityConstants
+    @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
+    private lateinit var scope: CoroutineScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
+        wifiRepository.setIsWifiEnabled(true)
         interactor = WifiInteractor(connectivityRepository, wifiRepository)
-
-        underTest = WifiViewModel(
-            statusBarPipelineFlags,
-            constants,
-            context,
-            logger,
-            interactor
-        )
+        scope = CoroutineScope(IMMEDIATE)
+        createAndSetViewModel()
     }
 
-    @Test
-    fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
-        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
 
-        var latest: Icon? = null
-        val job = underTest
+    // Note on testing: [WifiViewModel] exposes 3 different instances of
+    // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
+    // same data for icon, activity, etc. flows. So, most of these tests will test just one of the
+    // instances. There are also some tests that verify all 3 instances received the same data.
+
+    @Test
+    fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+        var latestHome: Icon? = null
+        val jobHome = underTest
+            .home
             .wifiIcon
-            .onEach { latest = it }
+            .onEach { latestHome = it }
             .launchIn(this)
 
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
-        connectivityRepository.setForceHiddenIcons(setOf())
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
-
-        var latest: Icon? = null
-        val job = underTest
+        var latestKeyguard: Icon? = null
+        val jobKeyguard = underTest
+            .keyguard
             .wifiIcon
-            .onEach { latest = it }
+            .onEach { latestKeyguard = it }
             .launchIn(this)
 
-        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
-
-        var latest: Icon? = null
-        val job = underTest
-                .wifiIcon
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
-        val icon = latest as Icon.Resource
-        assertThat(icon.res).isEqualTo(WIFI_NO_NETWORK)
-        assertThat(icon.contentDescription?.getAsString())
-            .contains(context.getString(WIFI_NO_CONNECTION))
-        assertThat(icon.contentDescription?.getAsString())
-            .contains(context.getString(NO_INTERNET))
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
-
-        var latest: Icon? = null
-        val job = underTest
+        var latestQs: Icon? = null
+        val jobQs = underTest
+            .qs
             .wifiIcon
-            .onEach { latest = it }
+            .onEach { latestQs = it }
             .launchIn(this)
 
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
-
-        var latest: Icon? = null
-        val job = underTest
-            .wifiIcon
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
-        val level = 1
-
         wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
-                        NETWORK_ID,
-                        isValidated = true,
-                        level = level
-                )
-        )
-
-        var latest: Icon? = null
-        val job = underTest
-            .wifiIcon
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
-        val icon = latest as Icon.Resource
-        assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
-        assertThat(icon.contentDescription?.getAsString())
-            .contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
-        assertThat(icon.contentDescription?.getAsString())
-            .doesNotContain(context.getString(NO_INTERNET))
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
-        val level = 4
-
-        wifiRepository.setWifiNetwork(
-                WifiNetworkModel.Active(
-                        NETWORK_ID,
-                        isValidated = false,
-                        level = level
-                )
-        )
-
-        var latest: Icon? = null
-        val job = underTest
-            .wifiIcon
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
-        val icon = latest as Icon.Resource
-        assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
-        assertThat(icon.contentDescription?.getAsString())
-            .contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
-        assertThat(icon.contentDescription?.getAsString())
-            .contains(context.getString(NO_INTERNET))
-
-        job.cancel()
-    }
-
-    @Test
-    fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(constants.shouldShowActivityConfig).thenReturn(false)
-        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
-
-        var latest: Boolean? = null
-        val job = underTest
-                .isActivityInVisible
-                .onEach { latest = it }
-                .launchIn(this)
-
-        // Verify that on launch, we receive a false.
-        assertThat(latest).isFalse()
-
-        job.cancel()
-    }
-
-    @Test
-    fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
-        whenever(constants.shouldShowActivityConfig).thenReturn(false)
-        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
-
-        var latest: Boolean? = null
-        val job = underTest
-                .isActivityInVisible
-                .onEach { latest = it }
-                .launchIn(this)
-
-        // Update the repo to have activityIn
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+            WifiNetworkModel.Active(
+                NETWORK_ID,
+                isValidated = true,
+                level = 1
+            )
         )
         yield()
 
-        // Verify that we didn't update to activityIn=true (because our config is false)
-        assertThat(latest).isFalse()
+        assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+        assertThat(latestHome).isEqualTo(latestKeyguard)
+        assertThat(latestKeyguard).isEqualTo(latestQs)
 
-        job.cancel()
+        jobHome.cancel()
+        jobKeyguard.cancel()
+        jobQs.cancel()
     }
 
     @Test
-    fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
-        whenever(constants.shouldShowActivityConfig).thenReturn(true)
+    fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var activityIn: Boolean? = null
+        val activityInJob = underTest
+            .home
+            .isActivityInViewVisible
+            .onEach { activityIn = it }
+            .launchIn(this)
+
+        var activityOut: Boolean? = null
+        val activityOutJob = underTest
+            .home
+            .isActivityOutViewVisible
+            .onEach { activityOut = it }
+            .launchIn(this)
+
+        var activityContainer: Boolean? = null
+        val activityContainerJob = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { activityContainer = it }
+            .launchIn(this)
+
+        // Verify that on launch, we receive false.
+        assertThat(activityIn).isFalse()
+        assertThat(activityOut).isFalse()
+        assertThat(activityContainer).isFalse()
+
+        activityInJob.cancel()
+        activityOutJob.cancel()
+        activityContainerJob.cancel()
+    }
+
+    @Test
+    fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var activityIn: Boolean? = null
+        val activityInJob = underTest
+            .home
+            .isActivityInViewVisible
+            .onEach { activityIn = it }
+            .launchIn(this)
+
+        var activityOut: Boolean? = null
+        val activityOutJob = underTest
+            .home
+            .isActivityOutViewVisible
+            .onEach { activityOut = it }
+            .launchIn(this)
+
+        var activityContainer: Boolean? = null
+        val activityContainerJob = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { activityContainer = it }
+            .launchIn(this)
+
+        // WHEN we update the repo to have activity
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        // THEN we didn't update to the new activity (because our config is false)
+        assertThat(activityIn).isFalse()
+        assertThat(activityOut).isFalse()
+        assertThat(activityContainer).isFalse()
+
+        activityInJob.cancel()
+        activityOutJob.cancel()
+        activityContainerJob.cancel()
+    }
+
+    @Test
+    fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
+
+        var activityIn: Boolean? = null
+        val activityInJob = underTest
+            .home
+            .isActivityInViewVisible
+            .onEach { activityIn = it }
+            .launchIn(this)
+
+        var activityOut: Boolean? = null
+        val activityOutJob = underTest
+            .home
+            .isActivityOutViewVisible
+            .onEach { activityOut = it }
+            .launchIn(this)
+
+        var activityContainer: Boolean? = null
+        val activityContainerJob = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { activityContainer = it }
+            .launchIn(this)
+
+        // WHEN we update the repo to have activity
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        // THEN we still output false because our network's SSID is null
+        assertThat(activityIn).isFalse()
+        assertThat(activityOut).isFalse()
+        assertThat(activityContainer).isFalse()
+
+        activityInJob.cancel()
+        activityOutJob.cancel()
+        activityContainerJob.cancel()
+    }
+
+    @Test
+    fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latestHome: Boolean? = null
+        val jobHome = underTest
+            .home
+            .isActivityInViewVisible
+            .onEach { latestHome = it }
+            .launchIn(this)
+
+        var latestKeyguard: Boolean? = null
+        val jobKeyguard = underTest
+            .keyguard
+            .isActivityInViewVisible
+            .onEach { latestKeyguard = it }
+            .launchIn(this)
+
+        var latestQs: Boolean? = null
+        val jobQs = underTest
+            .qs
+            .isActivityInViewVisible
+            .onEach { latestQs = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latestHome).isTrue()
+        assertThat(latestKeyguard).isTrue()
+        assertThat(latestQs).isTrue()
+
+        jobHome.cancel()
+        jobKeyguard.cancel()
+        jobQs.cancel()
+    }
+
+    @Test
+    fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
-                .isActivityInVisible
-                .onEach { latest = it }
-                .launchIn(this)
+            .home
+            .isActivityInViewVisible
+            .onEach { latest = it }
+            .launchIn(this)
 
-        // Update the repo to have activityIn
-        wifiRepository.setWifiActivity(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
-        )
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity)
         yield()
 
-        // Verify that we updated to activityIn=true
         assertThat(latest).isTrue()
 
         job.cancel()
     }
 
-    private fun ContentDescription.getAsString(): String? {
-        return when (this) {
-            is ContentDescription.Loaded -> this.description
-            is ContentDescription.Resource -> context.getString(this.res)
-        }
+    @Test
+    fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityInViewVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityOutViewVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityOutViewVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        createAndSetViewModel()
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+        var latest: Boolean? = null
+        val job = underTest
+            .home
+            .isActivityContainerVisible
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity)
+        yield()
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    private fun createAndSetViewModel() {
+        // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
+        // creations rely on certain config values that we mock out in individual tests. This method
+        // allows tests to create the view model only after those configs are correctly set up.
+        underTest = WifiViewModel(
+            connectivityConstants,
+            context,
+            logger,
+            interactor,
+            scope,
+            statusBarPipelineFlags,
+            wifiConstants,
+        )
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
new file mode 100644
index 0000000..f304647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.policy
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.lang.ref.WeakReference
+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.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BaseUserSwitcherAdapterTest : SysuiTestCase() {
+
+    @Mock private lateinit var controller: UserSwitcherController
+
+    private lateinit var underTest: BaseUserSwitcherAdapter
+
+    private lateinit var users: ArrayList<UserRecord>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        users =
+            ArrayList(
+                listOf(
+                    createUserRecord(
+                        id = 0,
+                        picture = mock(),
+                        isSelected = true,
+                        isGuest = false,
+                    ),
+                    createUserRecord(
+                        id = 1,
+                        picture = mock(),
+                        isSelected = false,
+                        isGuest = false,
+                    ),
+                    createUserRecord(
+                        id = UserHandle.USER_NULL,
+                        picture = null,
+                        isSelected = false,
+                        isGuest = true,
+                    ),
+                )
+            )
+
+        whenever(controller.users).thenAnswer { users }
+
+        underTest =
+            object : BaseUserSwitcherAdapter(controller) {
+                override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
+                    return mock()
+                }
+            }
+    }
+
+    @Test
+    fun `Adds self to controller in constructor`() {
+        val captor = kotlinArgumentCaptor<WeakReference<BaseUserSwitcherAdapter>>()
+        verify(controller).addAdapter(captor.capture())
+
+        assertThat(captor.value.get()).isEqualTo(underTest)
+    }
+
+    @Test
+    fun count() {
+        assertThat(underTest.count).isEqualTo(users.size)
+    }
+
+    @Test
+    fun `count - ignores restricted users when device is locked`() {
+        whenever(controller.isKeyguardShowing).thenReturn(true)
+        users =
+            ArrayList(
+                listOf(
+                    createUserRecord(
+                        id = 0,
+                        picture = mock(),
+                        isSelected = true,
+                        isGuest = false,
+                        isRestricted = false,
+                    ),
+                    createUserRecord(
+                        id = 1,
+                        picture = mock(),
+                        isSelected = false,
+                        isGuest = false,
+                        isRestricted = true, // this one will be ignored.
+                    ),
+                    createUserRecord(
+                        id = UserHandle.USER_NULL,
+                        picture = null,
+                        isSelected = false,
+                        isGuest = true,
+                    ),
+                )
+            )
+        assertThat(underTest.count).isEqualTo(users.size - 1)
+    }
+
+    @Test
+    fun `count - does not ignore restricted users when device is not locked`() {
+        whenever(controller.isKeyguardShowing).thenReturn(false)
+        users =
+            ArrayList(
+                listOf(
+                    createUserRecord(
+                        id = 0,
+                        picture = mock(),
+                        isSelected = true,
+                        isGuest = false,
+                        isRestricted = false,
+                    ),
+                    createUserRecord(
+                        id = 1,
+                        picture = mock(),
+                        isSelected = false,
+                        isGuest = false,
+                        isRestricted = true,
+                    ),
+                    createUserRecord(
+                        id = UserHandle.USER_NULL,
+                        picture = null,
+                        isSelected = false,
+                        isGuest = true,
+                    ),
+                )
+            )
+        assertThat(underTest.count).isEqualTo(users.size)
+    }
+
+    @Test
+    fun getItem() {
+        assertThat((0 until underTest.count).map { position -> underTest.getItem(position) })
+            .isEqualTo(users)
+    }
+
+    @Test
+    fun getItemId() {
+        (0 until underTest.count).map { position ->
+            assertThat(underTest.getItemId(position)).isEqualTo(position)
+        }
+    }
+
+    @Test
+    fun onUserListItemClicked() {
+        val userRecord = users[users.size / 2]
+        val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+        underTest.onUserListItemClicked(userRecord, dialogShower)
+
+        verify(controller).onUserListItemClicked(userRecord, dialogShower)
+    }
+
+    @Test
+    fun `getName - non guest - returns real name`() {
+        val userRecord =
+            createUserRecord(
+                id = 1,
+                picture = mock(),
+            )
+
+        assertThat(underTest.getName(context, userRecord)).isEqualTo(userRecord.info?.name)
+    }
+
+    @Test
+    fun `getName - guest and selected - returns exit guest action name`() {
+        val expected = "Exit guest"
+        context.orCreateTestableResources.addOverride(
+            com.android.settingslib.R.string.guest_exit_quick_settings_button,
+            expected,
+        )
+
+        val userRecord =
+            createUserRecord(
+                id = 2,
+                picture = null,
+                isGuest = true,
+                isSelected = true,
+            )
+
+        assertThat(underTest.getName(context, userRecord)).isEqualTo(expected)
+    }
+
+    @Test
+    fun `getName - guest and not selected - returns enter guest action name`() {
+        val expected = "Guest"
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.string.guest_name,
+            expected,
+        )
+
+        val userRecord =
+            createUserRecord(
+                id = 2,
+                picture = null,
+                isGuest = true,
+                isSelected = false,
+            )
+
+        assertThat(underTest.getName(context, userRecord)).isEqualTo("Guest")
+    }
+
+    @Test
+    fun refresh() {
+        underTest.refresh()
+
+        verify(controller).refreshUsers(UserHandle.USER_NULL)
+    }
+
+    private fun createUserRecord(
+        id: Int,
+        picture: Bitmap? = null,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+        isAction: Boolean = false,
+        isRestricted: Boolean = false,
+    ): UserRecord {
+        return UserRecord(
+            info =
+                if (isAction) {
+                    null
+                } else {
+                    UserInfo(id, "name$id", 0)
+                },
+            picture = picture,
+            isCurrent = isSelected,
+            isGuest = isGuest,
+            isRestricted = isRestricted,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index b4f3987b..b86ca6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -38,9 +38,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -102,8 +102,7 @@
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
-        `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
-        `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
+        `when`(userSwitcherController.isKeyguardShowing).thenReturn(true)
         `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
         keyguardQsUserSwitchController.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index c47ea9c..6ace404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -275,10 +275,9 @@
         EditText editText = view.findViewById(R.id.remote_input_text);
         editText.setText(TEST_REPLY);
         ClipDescription description = new ClipDescription("", new String[] {"image/png"});
-        // We need to use an (arbitrary) real resource here so that an actual image gets attached.
+        // We need to use an (arbitrary) real resource here so that an actual image gets attached
         ClipData clip = new ClipData(description, new ClipData.Item(
-                Uri.parse("android.resource://com.android.systemui/"
-                        + R.drawable.default_thumbnail)));
+                Uri.parse("android.resource://android/" + android.R.drawable.btn_default)));
         ContentInfo payload =
                 new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
         view.setAttachment(payload);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
index 8dcd4bb..76ecc1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
@@ -86,7 +86,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
-class UserSwitcherControllerTest : SysuiTestCase() {
+class UserSwitcherControllerOldImplTest : SysuiTestCase() {
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var activityManager: IActivityManager
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@@ -118,7 +118,7 @@
     private lateinit var longRunningExecutor: FakeExecutor
     private lateinit var uiExecutor: FakeExecutor
     private lateinit var uiEventLogger: UiEventLoggerFake
-    private lateinit var userSwitcherController: UserSwitcherController
+    private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
     private lateinit var picture: Bitmap
     private val ownerId = UserHandle.USER_SYSTEM
     private val ownerInfo = UserInfo(ownerId, "Owner", null,
@@ -205,7 +205,8 @@
     }
 
     private fun setupController() {
-        userSwitcherController = UserSwitcherController(
+        userSwitcherController =
+            UserSwitcherControllerOldImpl(
                 mContext,
                 activityManager,
                 userManager,
@@ -230,7 +231,8 @@
                 dumpManager,
                 dialogLaunchAnimator,
                 guestResumeSessionReceiver,
-                guestResetOrExitSessionReceiver)
+                guestResetOrExitSessionReceiver
+            )
         userSwitcherController.init(notificationShadeWindowView)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
new file mode 100644
index 0000000..773a0d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.telephony.data.repository
+
+import android.telephony.TelephonyCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+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
+
+@SmallTest
+@RunWith(JUnit4::class)
+class TelephonyRepositoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var manager: TelephonyListenerManager
+
+    private lateinit var underTest: TelephonyRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            TelephonyRepositoryImpl(
+                manager = manager,
+            )
+    }
+
+    @Test
+    fun callState() =
+        runBlocking(IMMEDIATE) {
+            var callState: Int? = null
+            val job = underTest.callState.onEach { callState = it }.launchIn(this)
+            val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+            verify(manager).addCallStateListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+
+            listener.onCallStateChanged(0)
+            assertThat(callState).isEqualTo(0)
+
+            listener.onCallStateChanged(1)
+            assertThat(callState).isEqualTo(1)
+
+            listener.onCallStateChanged(2)
+            assertThat(callState).isEqualTo(2)
+
+            job.cancel()
+
+            verify(manager).removeCallStateListener(listener)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
new file mode 100644
index 0000000..4a8e055
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.repository
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
+
+    @Before
+    fun setUp() {
+        super.setUp(isRefactored = true)
+    }
+
+    @Test
+    fun userSwitcherSettings() = runSelfCancelingTest {
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = true,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = true,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = false,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+    }
+
+    @Test
+    fun refreshUsers() = runSelfCancelingTest {
+        underTest = create(this)
+        val initialExpectedValue =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(initialExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val secondExpectedValue =
+            setUpUsers(
+                count = 4,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(secondExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val selectedNonGuestUserId = selectedUserInfo?.id
+        val thirdExpectedValue =
+            setUpUsers(
+                count = 2,
+                hasGuest = true,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(thirdExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+        assertThat(selectedUserInfo?.isGuest).isTrue()
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+    }
+
+    private fun setUpUsers(
+        count: Int,
+        hasGuest: Boolean = false,
+        selectedIndex: Int = 0,
+    ): List<UserInfo> {
+        val userInfos =
+            (0 until count).map { index ->
+                createUserInfo(
+                    index,
+                    isGuest = hasGuest && index == count - 1,
+                )
+            }
+        whenever(manager.aliveUsers).thenReturn(userInfos)
+        tracker.set(userInfos, selectedIndex)
+        return userInfos
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        isGuest: Boolean,
+    ): UserInfo {
+        val flags = 0
+        return UserInfo(
+            id,
+            "user_$id",
+            /* iconPath= */ "",
+            flags,
+            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+        )
+    }
+
+    private fun setUpGlobalSettings(
+        isSimpleUserSwitcher: Boolean = false,
+        isAddUsersFromLockscreen: Boolean = false,
+        isUserSwitcherEnabled: Boolean = true,
+    ) {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+            true,
+        )
+        globalSettings.putIntForUser(
+            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+            if (isSimpleUserSwitcher) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.ADD_USERS_WHEN_LOCKED,
+            if (isAddUsersFromLockscreen) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.USER_SWITCHER_ENABLED,
+            if (isUserSwitcherEnabled) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+    }
+
+    private fun assertUserSwitcherSettings(
+        model: UserSwitcherSettingsModel?,
+        expectedSimpleUserSwitcher: Boolean,
+        expectedAddUsersFromLockscreen: Boolean,
+        expectedUserSwitcherEnabled: Boolean,
+    ) {
+        checkNotNull(model)
+        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(Dispatchers.Main.immediate) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+}
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 6b466e1..dcea83a 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
@@ -17,201 +17,54 @@
 
 package com.android.systemui.user.data.repository
 
-import android.content.pm.UserInfo
 import android.os.UserManager
-import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
+import kotlinx.coroutines.test.TestCoroutineScope
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplTest : SysuiTestCase() {
+abstract class UserRepositoryImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var manager: UserManager
-    @Mock private lateinit var controller: UserSwitcherController
-    @Captor
-    private lateinit var userSwitchCallbackCaptor:
-        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+    @Mock protected lateinit var manager: UserManager
+    @Mock protected lateinit var controller: UserSwitcherController
 
-    private lateinit var underTest: UserRepositoryImpl
+    protected lateinit var underTest: UserRepositoryImpl
 
-    @Before
-    fun setUp() {
+    protected lateinit var globalSettings: FakeSettings
+    protected lateinit var tracker: FakeUserTracker
+    protected lateinit var featureFlags: FakeFeatureFlags
+
+    protected fun setUp(isRefactored: Boolean) {
         MockitoAnnotations.initMocks(this)
-        whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false))
-        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
-        whenever(controller.isGuestUserResetting).thenReturn(false)
 
-        underTest =
-            UserRepositoryImpl(
-                appContext = context,
-                manager = manager,
-                controller = controller,
-            )
+        globalSettings = FakeSettings()
+        tracker = FakeUserTracker()
+        featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
     }
 
-    @Test
-    fun `users - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `users - does not include actions`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                    )
-                )
-            var models: List<UserModel>? = null
-            val job = underTest.users.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)?.id).isEqualTo(0)
-            assertThat(models?.get(0)?.isSelected).isTrue()
-            assertThat(models?.get(1)?.id).isEqualTo(1)
-            assertThat(models?.get(1)?.isSelected).isFalse()
-            assertThat(models?.get(2)?.id).isEqualTo(2)
-            assertThat(models?.get(2)?.isSelected).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                    )
-                )
-            var id: Int? = null
-            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
-            assertThat(id).isEqualTo(0)
-
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0),
-                        createUserRecord(1),
-                        createUserRecord(2, isSelected = true),
-                    )
-                )
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-            userSwitchCallbackCaptor.value.onUserSwitched()
-            assertThat(id).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `actions - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actopms - does not include users`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                    )
-                )
-            var models: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
-            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
-            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
-            job.cancel()
-        }
-
-    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
-        return UserRecord(
-            info = UserInfo(id, "name$id", 0),
-            isCurrent = isSelected,
-        )
-    }
-
-    private fun createActionRecord(action: UserActionModel): UserRecord {
-        return UserRecord(
-            isAddUser = action == UserActionModel.ADD_USER,
-            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
-            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+    protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+        return UserRepositoryImpl(
+            appContext = context,
+            manager = manager,
+            controller = controller,
+            applicationScope = scope,
+            mainDispatcher = IMMEDIATE,
+            backgroundDispatcher = IMMEDIATE,
+            globalSettings = globalSettings,
+            tracker = tracker,
+            featureFlags = featureFlags,
         )
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
+        @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
new file mode 100644
index 0000000..d4b41c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+
+    @Captor
+    private lateinit var userSwitchCallbackCaptor:
+        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+
+    @Before
+    fun setUp() {
+        super.setUp(isRefactored = false)
+
+        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
+        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
+        whenever(controller.isGuestUserResetting).thenReturn(false)
+
+        underTest = create()
+    }
+
+    @Test
+    fun `users - registers for updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.users.onEach {}.launchIn(this)
+
+            verify(controller).addUserSwitchCallback(any())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - unregisters from updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.users.onEach {}.launchIn(this)
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+            job.cancel()
+
+            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+        }
+
+    @Test
+    fun `users - does not include actions`() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createActionRecord(UserActionModel.ADD_USER),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                    )
+                )
+            var models: List<UserModel>? = null
+            val job = underTest.users.onEach { models = it }.launchIn(this)
+
+            assertThat(models).hasSize(3)
+            assertThat(models?.get(0)?.id).isEqualTo(0)
+            assertThat(models?.get(0)?.isSelected).isTrue()
+            assertThat(models?.get(1)?.id).isEqualTo(1)
+            assertThat(models?.get(1)?.isSelected).isFalse()
+            assertThat(models?.get(2)?.id).isEqualTo(2)
+            assertThat(models?.get(2)?.isSelected).isFalse()
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                    )
+                )
+            var id: Int? = null
+            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
+
+            assertThat(id).isEqualTo(0)
+
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0),
+                        createUserRecord(1),
+                        createUserRecord(2, isSelected = true),
+                    )
+                )
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+            userSwitchCallbackCaptor.value.onUserSwitched()
+            assertThat(id).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - unregisters from updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.actions.onEach {}.launchIn(this)
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+            job.cancel()
+
+            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+        }
+
+    @Test
+    fun `actions - registers for updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.actions.onEach {}.launchIn(this)
+
+            verify(controller).addUserSwitchCallback(any())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - does not include users`() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createActionRecord(UserActionModel.ADD_USER),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                    )
+                )
+            var models: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { models = it }.launchIn(this)
+
+            assertThat(models).hasSize(3)
+            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
+            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
+            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+            job.cancel()
+        }
+
+    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
+        return UserRecord(
+            info = UserInfo(id, "name$id", 0),
+            isCurrent = isSelected,
+        )
+    }
+
+    private fun createActionRecord(action: UserActionModel): UserRecord {
+        return UserRecord(
+            isAddUser = action == UserActionModel.ADD_USER,
+            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
+            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
new file mode 100644
index 0000000..120bf79
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+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.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class GuestUserInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var showDialog: (ShowDialogRequestModel) -> Unit
+    @Mock private lateinit var dismissDialog: () -> Unit
+    @Mock private lateinit var selectUser: (Int) -> Unit
+    @Mock private lateinit var switchUser: (Int) -> Unit
+
+    private lateinit var underTest: GuestUserInteractor
+
+    private lateinit var scope: TestCoroutineScope
+    private lateinit var repository: FakeUserRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(manager.createGuest(any())).thenReturn(GUEST_USER_INFO)
+
+        scope = TestCoroutineScope()
+        repository = FakeUserRepository()
+        repository.setUserInfos(ALL_USERS)
+
+        underTest =
+            GuestUserInteractor(
+                applicationContext = context,
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                backgroundDispatcher = IMMEDIATE,
+                manager = manager,
+                repository = repository,
+                deviceProvisionedController = deviceProvisionedController,
+                devicePolicyManager = devicePolicyManager,
+                refreshUsersScheduler =
+                    RefreshUsersScheduler(
+                        applicationScope = scope,
+                        mainDispatcher = IMMEDIATE,
+                        repository = repository,
+                    ),
+                uiEventLogger = uiEventLogger,
+            )
+    }
+
+    @Test
+    fun `onDeviceBootCompleted - allowed to add - create guest`() =
+        runBlocking(IMMEDIATE) {
+            setAllowedToAdd()
+
+            underTest.onDeviceBootCompleted()
+
+            verify(manager).createGuest(any())
+            verify(deviceProvisionedController, never()).addCallback(any())
+        }
+
+    @Test
+    fun `onDeviceBootCompleted - await provisioning - and create guest`() =
+        runBlocking(IMMEDIATE) {
+            setAllowedToAdd(isAllowed = false)
+            underTest.onDeviceBootCompleted()
+            val captor =
+                kotlinArgumentCaptor<DeviceProvisionedController.DeviceProvisionedListener>()
+            verify(deviceProvisionedController).addCallback(captor.capture())
+
+            setAllowedToAdd(isAllowed = true)
+            captor.value.onDeviceProvisionedChanged()
+
+            verify(manager).createGuest(any())
+            verify(deviceProvisionedController).removeCallback(captor.value)
+        }
+
+    @Test
+    fun createAndSwitchTo() =
+        runBlocking(IMMEDIATE) {
+            underTest.createAndSwitchTo(
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                selectUser = selectUser,
+            )
+
+            verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            verify(manager).createGuest(any())
+            verify(dismissDialog).invoke()
+            verify(selectUser).invoke(GUEST_USER_INFO.id)
+        }
+
+    @Test
+    fun `createAndSwitchTo - fails to create - does not switch to`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.createGuest(any())).thenReturn(null)
+
+            underTest.createAndSwitchTo(
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                selectUser = selectUser,
+            )
+
+            verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            verify(manager).createGuest(any())
+            verify(dismissDialog).invoke()
+            verify(selectUser, never()).invoke(anyInt())
+        }
+
+    @Test
+    fun `exit - returns to target user`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            val targetUserId = NON_GUEST_USER_INFO.id
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - returns to last non-guest`() =
+        runBlocking(IMMEDIATE) {
+            val expectedUserId = NON_GUEST_USER_INFO.id
+            whenever(manager.getUserInfo(expectedUserId)).thenReturn(NON_GUEST_USER_INFO)
+            repository.lastSelectedNonGuestUserId = expectedUserId
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = UserHandle.USER_NULL,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(expectedUserId)
+        }
+
+    @Test
+    fun `exit - last non-guest was removed - returns to system`() =
+        runBlocking(IMMEDIATE) {
+            val removedUserId = 310
+            repository.lastSelectedNonGuestUserId = removedUserId
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = UserHandle.USER_NULL,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(UserHandle.USER_SYSTEM)
+        }
+
+    @Test
+    fun `exit - guest was ephemeral - it is removed`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
+            repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
+            val targetUserId = NON_GUEST_USER_INFO.id
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
+            verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - force remove guest - it is removed`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+            val targetUserId = NON_GUEST_USER_INFO.id
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = true,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - selected different from guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = 123,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotExit()
+        }
+
+    @Test
+    fun `exit - selected is actually not a guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = NON_GUEST_USER_INFO.id,
+                targetUserId = 123,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotExit()
+        }
+
+    @Test
+    fun `remove - returns to target user`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            val targetUserId = NON_GUEST_USER_INFO.id
+            underTest.remove(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `remove - selected different from guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.remove(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = 123,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotRemove()
+        }
+
+    @Test
+    fun `remove - selected is actually not a guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.remove(
+                guestUserId = NON_GUEST_USER_INFO.id,
+                targetUserId = 123,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotRemove()
+        }
+
+    private fun setAllowedToAdd(isAllowed: Boolean = true) {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(isAllowed)
+        whenever(devicePolicyManager.isDeviceManaged).thenReturn(!isAllowed)
+    }
+
+    private fun verifyDidNotExit() {
+        verifyDidNotRemove()
+        verify(manager, never()).getUserInfo(anyInt())
+        verify(uiEventLogger, never()).log(any())
+    }
+
+    private fun verifyDidNotRemove() {
+        verify(manager, never()).markGuestForDeletion(anyInt())
+        verify(showDialog, never()).invoke(any())
+        verify(dismissDialog, never()).invoke()
+        verify(switchUser, never()).invoke(anyInt())
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private val NON_GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 818,
+                /* name= */ "non_guest",
+                /* flags= */ 0,
+            )
+        private val GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 669,
+                /* name= */ "guest",
+                /* iconPath= */ "",
+                /* flags= */ 0,
+                UserManager.USER_TYPE_FULL_GUEST,
+            )
+        private val EPHEMERAL_GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 669,
+                /* name= */ "guest",
+                /* iconPath= */ "",
+                /* flags= */ UserInfo.FLAG_EPHEMERAL,
+                UserManager.USER_TYPE_FULL_GUEST,
+            )
+        private val ALL_USERS =
+            listOf(
+                NON_GUEST_USER_INFO,
+                GUEST_USER_INFO,
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
new file mode 100644
index 0000000..593ce1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.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.systemui.user.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RefreshUsersSchedulerTest : SysuiTestCase() {
+
+    private lateinit var underTest: RefreshUsersScheduler
+
+    private lateinit var repository: FakeUserRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        repository = FakeUserRepository()
+    }
+
+    @Test
+    fun `pause - prevents the next refresh from happening`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.pause()
+
+            underTest.refreshIfNotPaused()
+            assertThat(repository.refreshUsersCallCount).isEqualTo(0)
+        }
+
+    @Test
+    fun `unpauseAndRefresh - forces the refresh even when paused`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.pause()
+
+            underTest.unpauseAndRefresh()
+
+            assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+        }
+
+    @Test
+    fun `refreshIfNotPaused - refreshes when not paused`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.refreshIfNotPaused()
+
+            assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
new file mode 100644
index 0000000..3d5695a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+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.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+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.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorRefactoredTest : UserInteractorTest() {
+
+    override fun isRefactored(): Boolean {
+        return true
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+        overrideResource(R.dimen.max_avatar_size, 10)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_APP_PACKAGE,
+        )
+        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+    }
+
+    @Test
+    fun `users - switcher enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 3, includeGuest = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switches to second user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            userRepository.setSelectedUserInfo(userInfos[1])
+
+            assertUsers(models = value, count = 2, selectedIndex = 1)
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switcher not enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: UserModel? = null
+            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+            assertUser(value, id = 0, isSelected = true)
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            assertUser(value, id = 1, isSelected = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user not primary - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user is guest - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            assertThat(userInfos[1].isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked - only guest action is shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(true)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add user - dialog shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.executeAction(UserActionModel.ADD_USER)
+            assertThat(dialogRequest)
+                .isEqualTo(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = userInfos[0].userHandle,
+                        isKeyguardShowing = false,
+                        showEphemeralMessage = false,
+                    )
+                )
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add supervised user - starts activity`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            assertThat(intentCaptor.value.action)
+                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+        }
+
+    @Test
+    fun `executeAction - navigate to manage users`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+        }
+
+    @Test
+    fun `executeAction - guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+            val showDialogsJob =
+                underTest.dialogShowRequests
+                    .onEach {
+                        dialogRequests.add(it)
+                        if (it != null) {
+                            underTest.onDialogShown()
+                        }
+                    }
+                    .launchIn(this)
+            val dismissDialogsJob =
+                underTest.dialogDismissRequests
+                    .onEach {
+                        if (it != null) {
+                            underTest.onDialogDismissed()
+                        }
+                    }
+                    .launchIn(this)
+
+            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+            assertThat(dialogRequests)
+                .contains(
+                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+                )
+            verify(activityManager).switchUser(guestUserInfo.id)
+
+            showDialogsJob.cancel()
+            dismissDialogsJob.cancel()
+        }
+
+    @Test
+    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = guestUserInfo.id)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - not currently guest - switches users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id)
+
+            assertThat(dialogRequest).isNull()
+            verify(activityManager).switchUser(userInfos[1].id)
+            job.cancel()
+        }
+
+    @Test
+    fun `Telephony call state changes - refreshes users`() =
+        runBlocking(IMMEDIATE) {
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            telephonyRepository.setCallState(1)
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User switched broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val callback1: UserInteractor.UserCallback = mock()
+            val callback2: UserInteractor.UserCallback = mock()
+            underTest.addCallback(callback1)
+            underTest.addCallback(callback2)
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_SWITCHED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+                )
+            }
+
+            verify(callback1).onUserStateChanged()
+            verify(callback2).onUserStateChanged()
+            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User info changed broadcast`() =
+        runBlocking(IMMEDIATE) {
+            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),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `System user unlocked broadcast - refresh users`() =
+        runBlocking(IMMEDIATE) {
+            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),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `Non-system user unlocked broadcast - do not refresh users`() =
+        runBlocking(IMMEDIATE) {
+            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),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+        }
+
+    @Test
+    fun userRecords() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    ),
+            )
+        }
+
+    @Test
+    fun selectedUserRecord() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertRecordForUser(
+                record = underTest.selectedUserRecord.value,
+                id = 0,
+                hasPicture = true,
+                isCurrent = true,
+                isSwitchToEnabled = true,
+            )
+        }
+
+    private fun assertUsers(
+        models: List<UserModel>?,
+        count: Int,
+        selectedIndex: Int = 0,
+        includeGuest: Boolean = false,
+    ) {
+        checkNotNull(models)
+        assertThat(models.size).isEqualTo(count)
+        models.forEachIndexed { index, model ->
+            assertUser(
+                model = model,
+                id = index,
+                isSelected = index == selectedIndex,
+                isGuest = includeGuest && index == count - 1
+            )
+        }
+    }
+
+    private fun assertUser(
+        model: UserModel?,
+        id: Int,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+    ) {
+        checkNotNull(model)
+        assertThat(model.id).isEqualTo(id)
+        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+        assertThat(model.isSelected).isEqualTo(isSelected)
+        assertThat(model.isSelectable).isTrue()
+        assertThat(model.isGuest).isEqualTo(isGuest)
+    }
+
+    private fun assertRecords(
+        records: List<UserRecord>,
+        userIds: List<Int>,
+        selectedUserIndex: Int = 0,
+        includeGuest: Boolean = false,
+        expectedActions: List<UserActionModel> = emptyList(),
+    ) {
+        assertThat(records.size >= userIds.size).isTrue()
+        userIds.indices.forEach { userIndex ->
+            val record = records[userIndex]
+            assertThat(record.info).isNotNull()
+            val isGuest = includeGuest && userIndex == userIds.size - 1
+            assertRecordForUser(
+                record = record,
+                id = userIds[userIndex],
+                hasPicture = !isGuest,
+                isCurrent = userIndex == selectedUserIndex,
+                isGuest = isGuest,
+                isSwitchToEnabled = true,
+            )
+        }
+
+        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+            val record = records[actionIndex]
+            assertThat(record.info).isNull()
+            assertRecordForAction(
+                record = record,
+                type = expectedActions[actionIndex - userIds.size],
+            )
+        }
+    }
+
+    private fun assertRecordForUser(
+        record: UserRecord?,
+        id: Int? = null,
+        hasPicture: Boolean = false,
+        isCurrent: Boolean = false,
+        isGuest: Boolean = false,
+        isSwitchToEnabled: Boolean = false,
+    ) {
+        checkNotNull(record)
+        assertThat(record.info?.id).isEqualTo(id)
+        assertThat(record.picture != null).isEqualTo(hasPicture)
+        assertThat(record.isCurrent).isEqualTo(isCurrent)
+        assertThat(record.isGuest).isEqualTo(isGuest)
+        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+    }
+
+    private fun assertRecordForAction(
+        record: UserRecord,
+        type: UserActionModel,
+    ) {
+        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+        assertThat(record.isAddSupervisedUser)
+            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+    }
+
+    private fun createUserInfos(
+        count: Int,
+        includeGuest: Boolean,
+    ): List<UserInfo> {
+        return (0 until count).map { index ->
+            val isGuest = includeGuest && index == count - 1
+            createUserInfo(
+                id = index,
+                name =
+                    if (isGuest) {
+                        "guest"
+                    } else {
+                        "user_$index"
+                    },
+                isPrimary = !isGuest && index == 0,
+                isGuest = isGuest,
+            )
+        }
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        name: String,
+        isPrimary: Boolean = false,
+        isGuest: Boolean = false,
+    ): UserInfo {
+        return UserInfo(
+            id,
+            name,
+            /* iconPath= */ "",
+            /* flags= */ if (isPrimary) {
+                UserInfo.FLAG_PRIMARY
+            } else {
+                0
+            },
+            if (isGuest) {
+                UserManager.USER_TYPE_FULL_GUEST
+            } else {
+                UserManager.USER_TYPE_FULL_SYSTEM
+            },
+        )
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val GUEST_ICON: Drawable = mock()
+        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
+    }
+}
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 e914e2e..8465f4f 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
@@ -17,51 +17,61 @@
 
 package com.android.systemui.user.domain.interactor
 
-import androidx.test.filters.SmallTest
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.os.UserManager
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+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.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import kotlinx.coroutines.test.TestCoroutineScope
 import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+abstract class UserInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var controller: UserSwitcherController
-    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock protected lateinit var controller: UserSwitcherController
+    @Mock protected lateinit var activityStarter: ActivityStarter
+    @Mock protected lateinit var manager: UserManager
+    @Mock protected lateinit var activityManager: ActivityManager
+    @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock protected lateinit var uiEventLogger: UiEventLogger
 
-    private lateinit var underTest: UserInteractor
+    protected lateinit var underTest: UserInteractor
 
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
+    protected lateinit var testCoroutineScope: TestCoroutineScope
+    protected lateinit var userRepository: FakeUserRepository
+    protected lateinit var keyguardRepository: FakeKeyguardRepository
+    protected lateinit var telephonyRepository: FakeTelephonyRepository
 
-    @Before
-    fun setUp() {
+    abstract fun isRefactored(): Boolean
+
+    open fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
+        telephonyRepository = FakeTelephonyRepository()
+        testCoroutineScope = TestCoroutineScope()
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = testCoroutineScope,
+                mainDispatcher = IMMEDIATE,
+                repository = userRepository,
+            )
         underTest =
             UserInteractor(
+                applicationContext = context,
                 repository = userRepository,
                 controller = controller,
                 activityStarter = activityStarter,
@@ -69,142 +79,34 @@
                     KeyguardInteractor(
                         repository = keyguardRepository,
                     ),
-            )
-    }
-
-    @Test
-    fun `actions - not actionable when locked and locked - no actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions).isEmpty()
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - not actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
+                    },
+                manager = manager,
+                applicationScope = testCoroutineScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = telephonyRepository,
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                backgroundDispatcher = IMMEDIATE,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor =
+                    GuestUserInteractor(
+                        applicationContext = context,
+                        applicationScope = testCoroutineScope,
+                        mainDispatcher = IMMEDIATE,
+                        backgroundDispatcher = IMMEDIATE,
+                        manager = manager,
+                        repository = userRepository,
+                        deviceProvisionedController = deviceProvisionedController,
+                        devicePolicyManager = devicePolicyManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        uiEventLogger = uiEventLogger,
                     )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
             )
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun selectUser() {
-        val userId = 3
-
-        underTest.selectUser(userId)
-
-        verify(controller).onUserSelected(eq(userId), nullable())
-    }
-
-    @Test
-    fun `executeAction - guest`() {
-        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-        verify(controller).createAndSwitchToGuestUser(nullable())
-    }
-
-    @Test
-    fun `executeAction - add user`() {
-        underTest.executeAction(UserActionModel.ADD_USER)
-
-        verify(controller).showAddUserDialog(nullable())
-    }
-
-    @Test
-    fun `executeAction - add supervised user`() {
-        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-        verify(controller).startSupervisedUserActivity()
-    }
-
-    @Test
-    fun `executeAction - manage users`() {
-        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-        verify(activityStarter).startActivity(any(), anyBoolean())
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
new file mode 100644
index 0000000..c3a9705
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+open class UserInteractorUnrefactoredTest : UserInteractorTest() {
+
+    override fun isRefactored(): Boolean {
+        return false
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+    }
+
+    @Test
+    fun `actions - not actionable when locked and locked - no actions`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(UserActionModel.values().toList())
+            userRepository.setActionableWhenLocked(false)
+            keyguardRepository.setKeyguardShowing(true)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions).isEmpty()
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - not actionable when locked and not locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(false)
+            keyguardRepository.setKeyguardShowing(false)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - actionable when locked and not locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(true)
+            keyguardRepository.setKeyguardShowing(false)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - actionable when locked and locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(true)
+            keyguardRepository.setKeyguardShowing(true)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun selectUser() {
+        val userId = 3
+
+        underTest.selectUser(userId)
+
+        verify(controller).onUserSelected(eq(userId), nullable())
+    }
+
+    @Test
+    fun `executeAction - guest`() {
+        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+        verify(controller).createAndSwitchToGuestUser(nullable())
+    }
+
+    @Test
+    fun `executeAction - add user`() {
+        underTest.executeAction(UserActionModel.ADD_USER)
+
+        verify(controller).showAddUserDialog(nullable())
+    }
+
+    @Test
+    fun `executeAction - add supervised user`() {
+        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+        verify(controller).startSupervisedUserActivity()
+    }
+
+    @Test
+    fun `executeAction - manage users`() {
+        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+        verify(activityStarter).startActivity(any(), anyBoolean())
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
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 ef4500d..0344e3f 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
@@ -17,17 +17,28 @@
 
 package com.android.systemui.user.ui.viewmodel
 
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
 import android.graphics.drawable.Drawable
+import android.os.UserManager
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 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.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
@@ -38,6 +49,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
@@ -52,6 +64,11 @@
 
     @Mock private lateinit var controller: UserSwitcherController
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
 
     private lateinit var underTest: UserSwitcherViewModel
 
@@ -66,22 +83,60 @@
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
         powerRepository = FakePowerRepository()
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
+        val scope = TestCoroutineScope()
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                repository = userRepository,
+            )
+        val guestUserInteractor =
+            GuestUserInteractor(
+                applicationContext = context,
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                backgroundDispatcher = IMMEDIATE,
+                manager = manager,
+                repository = userRepository,
+                deviceProvisionedController = deviceProvisionedController,
+                devicePolicyManager = devicePolicyManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                uiEventLogger = uiEventLogger,
+            )
+
         underTest =
             UserSwitcherViewModel.Factory(
                     userInteractor =
                         UserInteractor(
+                            applicationContext = context,
                             repository = userRepository,
                             controller = controller,
                             activityStarter = activityStarter,
                             keyguardInteractor =
                                 KeyguardInteractor(
                                     repository = keyguardRepository,
-                                )
+                                ),
+                            featureFlags = featureFlags,
+                            manager = manager,
+                            applicationScope = scope,
+                            telephonyInteractor =
+                                TelephonyInteractor(
+                                    repository = FakeTelephonyRepository(),
+                                ),
+                            broadcastDispatcher = fakeBroadcastDispatcher,
+                            backgroundDispatcher = IMMEDIATE,
+                            activityManager = activityManager,
+                            refreshUsersScheduler = refreshUsersScheduler,
+                            guestUserInteractor = guestUserInteractor,
                         ),
                     powerInteractor =
                         PowerInteractor(
                             repository = powerRepository,
                         ),
+                    featureFlags = featureFlags,
+                    guestUserInteractor = guestUserInteractor,
                 )
                 .create(UserSwitcherViewModel::class.java)
     }
@@ -97,6 +152,7 @@
                         image = USER_IMAGE,
                         isSelected = true,
                         isSelectable = true,
+                        isGuest = false,
                     ),
                     UserModel(
                         id = 1,
@@ -104,6 +160,7 @@
                         image = USER_IMAGE,
                         isSelected = false,
                         isSelectable = true,
+                        isGuest = false,
                     ),
                     UserModel(
                         id = 2,
@@ -111,6 +168,7 @@
                         image = USER_IMAGE,
                         isSelected = false,
                         isSelectable = false,
+                        isGuest = false,
                     ),
                 )
             )
@@ -260,7 +318,7 @@
             job.cancel()
         }
 
-    private fun setUsers(count: Int) {
+    private suspend fun setUsers(count: Int) {
         userRepository.setUsers(
             (0 until count).map { index ->
                 UserModel(
@@ -269,6 +327,7 @@
                     image = USER_IMAGE,
                     isSelected = index == 0,
                     isSelectable = true,
+                    isGuest = false,
                 )
             }
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index aaf2188..3769f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -46,6 +46,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +100,8 @@
     private KeyguardManager mKeyguardManager;
     @Mock
     private ActivityManager mActivityManager;
+    @Mock
+    private DumpManager mDumpManager;
 
 
     @Before
@@ -121,7 +124,7 @@
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                 mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
-                mActivityManager, mCallback);
+                mActivityManager, mDumpManager, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -202,11 +205,12 @@
                 CaptioningManager captioningManager,
                 KeyguardManager keyguardManager,
                 ActivityManager activityManager,
+                DumpManager dumpManager,
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
                     packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
-                    activityManager);
+                    activityManager, dumpManager);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index e47acd8..c254358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
 
 package com.android.systemui.wallpapers;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
 
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -31,16 +40,25 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Surface;
 import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
 import org.junit.Before;
@@ -55,7 +73,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-@Ignore
 public class ImageWallpaperTest extends SysuiTestCase {
     private static final int LOW_BMP_WIDTH = 128;
     private static final int LOW_BMP_HEIGHT = 128;
@@ -65,13 +82,33 @@
     private static final int DISPLAY_HEIGHT = 1080;
 
     @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private WindowMetrics mWindowMetrics;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private WallpaperManager mWallpaperManager;
+    @Mock
     private SurfaceHolder mSurfaceHolder;
     @Mock
+    private Surface mSurface;
+    @Mock
     private Context mMockContext;
+
     @Mock
     private Bitmap mWallpaperBitmap;
+    private int mBitmapWidth = 1;
+    private int mBitmapHeight = 1;
+
     @Mock
     private Handler mHandler;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+
+    FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+    FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
 
     private CountDownLatch mEventCountdown;
 
@@ -79,28 +116,52 @@
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
-        mEventCountdown = new CountDownLatch(1);
+        //mEventCountdown = new CountDownLatch(1);
 
-        WallpaperManager wallpaperManager = mock(WallpaperManager.class);
+        // set up window manager
+        when(mWindowMetrics.getBounds()).thenReturn(
+                new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+
+        // set up display manager
+        doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+        when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+        // set up bitmap
+        when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+        when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+        when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+        when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+        // set up wallpaper manager
+        when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+                new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+        when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+        when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+        // set up surface
+        when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+        doNothing().when(mSurface).hwuiDestroy();
+
+        // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
         Resources resources = mock(Resources.class);
-
-        when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
-        when(mMockContext.getResources()).thenReturn(resources);
         when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-
+        when(mMockContext.getResources()).thenReturn(resources);
         DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.logicalWidth = DISPLAY_WIDTH;
         displayInfo.logicalHeight = DISPLAY_HEIGHT;
         when(mMockContext.getDisplay()).thenReturn(
                 new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+    }
 
-        when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
-        when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
-        when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+    private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+        mBitmapWidth = bitmapWidth;
+        mBitmapHeight = bitmapHeight;
     }
 
     private ImageWallpaper createImageWallpaper() {
-        return new ImageWallpaper() {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
             @Override
             public Engine onCreateEngine() {
                 return new GLEngine(mHandler) {
@@ -127,6 +188,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_normal() {
         // Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
         // Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -137,6 +199,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_low_resolution() {
         // Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
         // Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -147,6 +210,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_too_small() {
         // Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
         // Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -163,8 +227,7 @@
 
         ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
 
-        when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
-        when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+        setBitmapDimensions(bmpWidth, bmpHeight);
 
         ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
         doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -174,4 +237,116 @@
         assertWithMessage("setFixedSizeAllowed should have been called.").that(
                 mEventCountdown.getCount()).isEqualTo(0);
     }
+
+
+    private ImageWallpaper createImageWallpaperCanvas() {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+            @Override
+            public Engine onCreateEngine() {
+                return new CanvasEngine() {
+                    @Override
+                    public Context getDisplayContext() {
+                        return mMockContext;
+                    }
+
+                    @Override
+                    public SurfaceHolder getSurfaceHolder() {
+                        return mSurfaceHolder;
+                    }
+
+                    @Override
+                    public void setFixedSizeAllowed(boolean allowed) {
+                        super.setFixedSizeAllowed(allowed);
+                        assertWithMessage("mFixedSizeAllowed should be true").that(
+                                allowed).isTrue();
+                    }
+                };
+            }
+        };
+    }
+
+    private ImageWallpaper.CanvasEngine getSpyEngine() {
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+        doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+        doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+        doAnswer(invocation -> {
+            ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+            return null;
+        }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+        return spyEngine;
+    }
+
+    @Test
+    public void testMinSurface() {
+
+        // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+        testMinSurfaceHelper(8, 8);
+        testMinSurfaceHelper(100, 2000);
+        testMinSurfaceHelper(200, 1);
+        testMinSurfaceHelper(0, 1);
+        testMinSurfaceHelper(1, 0);
+        testMinSurfaceHelper(0, 0);
+    }
+
+    private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+        clearInvocations(mSurfaceHolder);
+        setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        engine.onCreate(mSurfaceHolder);
+
+        verify(mSurfaceHolder, times(1)).setFixedSize(
+                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+    }
+
+    @Test
+    public void testZeroBitmap() {
+        // test that a frame is never drawn with a 0 bitmap
+        testZeroBitmapHelper(0, 1);
+        testZeroBitmapHelper(1, 0);
+        testZeroBitmapHelper(0, 0);
+    }
+
+    private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+        clearInvocations(mSurfaceHolder);
+        setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+        spyEngine.onCreate(mSurfaceHolder);
+        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+        verify(spyEngine, never()).drawFrameOnCanvas(any());
+    }
+
+    @Test
+    public void testLoadDrawAndUnloadBitmap() {
+        setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+        ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+        spyEngine.onCreate(mSurfaceHolder);
+        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+        assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+        int n = 0;
+        while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+            n++;
+            assertThat(n).isAtMost(10);
+            mFakeBackgroundExecutor.runNextReady();
+            mFakeMainExecutor.runNextReady();
+            mFakeSystemClock.advanceTime(1000);
+        }
+
+        verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+        assertThat(spyEngine.isBitmapLoaded()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 0000000..76bff1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.canvas;
+
+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.doAnswer;
+import static org.mockito.Mockito.doNothing;
+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.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+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.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+    private static final int LOW_BMP_WIDTH = 128;
+    private static final int LOW_BMP_HEIGHT = 128;
+    private static final int HIGH_BMP_WIDTH = 3000;
+    private static final int HIGH_BMP_HEIGHT = 4000;
+    private static final int VERY_LOW_BMP_WIDTH = 1;
+    private static final int VERY_LOW_BMP_HEIGHT = 1;
+    private static final int DISPLAY_WIDTH = 1920;
+    private static final int DISPLAY_HEIGHT = 1080;
+
+    private static final int PAGES_LOW = 4;
+    private static final int PAGES_HIGH = 7;
+
+    private static final int MIN_AREAS = 4;
+    private static final int MAX_AREAS = 10;
+
+    private int mMiniBitmapWidth;
+    private int mMiniBitmapHeight;
+
+    @Mock
+    private Executor mBackgroundExecutor;
+
+    private int mColorsProcessed;
+    private int mMiniBitmapUpdatedCount;
+    private int mActivatedCount;
+    private int mDeactivatedCount;
+
+    @Before
+    public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation ->  {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mBackgroundExecutor).execute(any(Runnable.class));
+    }
+
+    private void resetCounters() {
+        mColorsProcessed = 0;
+        mMiniBitmapUpdatedCount = 0;
+        mActivatedCount = 0;
+        mDeactivatedCount = 0;
+    }
+
+    private Bitmap getMockBitmap(int width, int height) {
+        Bitmap bitmap = mock(Bitmap.class);
+        when(bitmap.getWidth()).thenReturn(width);
+        when(bitmap.getHeight()).thenReturn(height);
+        return bitmap;
+    }
+
+    private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+        WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+                mBackgroundExecutor,
+                new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+                    @Override
+                    public void onColorsProcessed(List<RectF> regions,
+                            List<WallpaperColors> colors) {
+                        assertThat(regions.size()).isEqualTo(colors.size());
+                        mColorsProcessed += regions.size();
+                    }
+
+                    @Override
+                    public void onMiniBitmapUpdated() {
+                        mMiniBitmapUpdatedCount++;
+                    }
+
+                    @Override
+                    public void onActivated() {
+                        mActivatedCount++;
+                    }
+
+                    @Override
+                    public void onDeactivated() {
+                        mDeactivatedCount++;
+                    }
+                });
+        WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+        doAnswer(invocation -> {
+            mMiniBitmapWidth = invocation.getArgument(1);
+            mMiniBitmapHeight = invocation.getArgument(2);
+            return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+        }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+        doAnswer(invocation -> getMockBitmap(
+                        invocation.getArgument(1),
+                        invocation.getArgument(2)))
+                .when(spyWallpaperColorExtractor)
+                .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+        doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+                .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+        return spyWallpaperColorExtractor;
+    }
+
+    private RectF randomArea() {
+        float width = (float) Math.random();
+        float startX = (float) (Math.random() * (1 - width));
+        float height = (float) Math.random();
+        float startY = (float) (Math.random() * (1 - height));
+        return new RectF(startX, startY, startX + width, startY + height);
+    }
+
+    private List<RectF> listOfRandomAreas(int min, int max) {
+        int nAreas = randomBetween(min, max);
+        List<RectF> result = new ArrayList<>();
+        for (int i = 0; i < nAreas; i++) {
+            result.add(randomArea());
+        }
+        return result;
+    }
+
+    private int randomBetween(int minIncluded, int maxIncluded) {
+        return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+    }
+
+    /**
+     * Test that for bitmaps of random dimensions, the mini bitmap is always created
+     * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+     */
+    @Test
+    public void testMiniBitmapCreation() {
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+            int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+            Bitmap bitmap = getMockBitmap(width, height);
+            spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+                    .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+        }
+    }
+
+    /**
+     * Test that for bitmaps with both width and height <= SMALL_SIDE,
+     * the mini bitmap is always created with both width and height <= SMALL_SIDE
+     */
+    @Test
+    public void testSmallMiniBitmapCreation() {
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+            int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+            Bitmap bitmap = getMockBitmap(width, height);
+            spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+                    .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+        }
+    }
+
+    /**
+     * Test that for a new color extractor with information
+     * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+     * the colors are processed and all the callbacks are properly executed.
+     */
+    @Test
+    public void testNewColorExtraction() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+            List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+            int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+            List<Runnable> tasks = Arrays.asList(
+                    () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+                    () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+                    () -> spyWallpaperColorExtractor.setDisplayDimensions(
+                            DISPLAY_WIDTH, DISPLAY_HEIGHT),
+                    () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+                            regions));
+            Collections.shuffle(tasks);
+            tasks.forEach(Runnable::run);
+
+            assertThat(mActivatedCount).isEqualTo(1);
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+            spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+            assertThat(mDeactivatedCount).isEqualTo(1);
+        }
+    }
+
+    /**
+     * Test that the method removeLocalColorAreas behaves properly and does not call
+     * the onDeactivated callback unless all color areas are removed.
+     */
+    @Test
+    public void testRemoveColors() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+            List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+            List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+            List<RectF> regions = new ArrayList<>();
+            regions.addAll(regions1);
+            regions.addAll(regions2);
+            int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+            List<Runnable> tasks = Arrays.asList(
+                    () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+                    () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+                    () -> spyWallpaperColorExtractor.setDisplayDimensions(
+                            DISPLAY_WIDTH, DISPLAY_HEIGHT),
+                    () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+            spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+            assertThat(mActivatedCount).isEqualTo(1);
+            Collections.shuffle(tasks);
+            tasks.forEach(Runnable::run);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(mDeactivatedCount).isEqualTo(0);
+            spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+            assertThat(mDeactivatedCount).isEqualTo(1);
+        }
+    }
+
+    /**
+     * Test that if we change some information (wallpaper bitmap, number of pages),
+     * the colors are correctly recomputed.
+     * Test that if we remove some color areas in the middle of the process,
+     * only the remaining areas are recomputed.
+     */
+    @Test
+    public void testRecomputeColorExtraction() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+        List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+        List<RectF> regions = new ArrayList<>();
+        regions.addAll(regions1);
+        regions.addAll(regions2);
+        spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+        assertThat(mActivatedCount).isEqualTo(1);
+        int nPages = PAGES_LOW;
+        spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+        spyWallpaperColorExtractor.onPageChanged(nPages);
+        spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+        int nSimulations = 20;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+
+            // verify that if we remove some regions, they are not recomputed after other changes
+            if (i == nSimulations / 2) {
+                regions.removeAll(regions2);
+                spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+            }
+
+            if (Math.random() >= 0.5) {
+                int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+                if (nPagesNew == nPages) continue;
+                nPages = nPagesNew;
+                spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+            } else {
+                Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+                spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+                assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            }
+            assertThat(mColorsProcessed).isEqualTo(regions.size());
+        }
+        spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+        assertThat(mDeactivatedCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testCleanUp() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        doNothing().when(bitmap).recycle();
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+        spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+        assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+        spyWallpaperColorExtractor.cleanUp();
+        spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+        assertThat(mColorsProcessed).isEqualTo(0);
+    }
+}
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 da33fa6..cebe946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,6 +34,7 @@
 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.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -74,16 +75,16 @@
     @Mock ProtoTracer mProtoTracer;
     @Mock UserTracker mUserTracker;
     @Mock ShellExecutor mSysUiMainExecutor;
+    @Mock FloatingTasks mFloatingTasks;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
         mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
-                Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue,
-                mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor,
-                mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
-                mUserTracker, mSysUiMainExecutor);
+                Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+                mCommandQueue, mConfigurationController, mKeyguardStateController,
+                mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
+                mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
     }
 
     @Test
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 53dcc8d..bb646f0 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
@@ -37,10 +37,18 @@
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger,
     userTracker: UserTracker
-) : BroadcastDispatcher(
-    context, looper, executor, dumpManager, logger, userTracker, PendingRemovalStore(logger)) {
+) :
+    BroadcastDispatcher(
+        context,
+        looper,
+        executor,
+        dumpManager,
+        logger,
+        userTracker,
+        PendingRemovalStore(logger)
+    ) {
 
-    private val registeredReceivers = ArraySet<BroadcastReceiver>()
+    val registeredReceivers = ArraySet<BroadcastReceiver>()
 
     override fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
@@ -78,4 +86,4 @@
         }
         registeredReceivers.clear()
     }
-}
\ No newline at end of file
+}
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 42b434a..725b1f4 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
@@ -44,6 +44,10 @@
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
+    override fun isKeyguardShowing(): Boolean {
+        return _isKeyguardShowing.value
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index b2b1764..9726bf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -26,20 +26,24 @@
 
 /** A fake [UserTracker] to be used in tests. */
 class FakeUserTracker(
-    userId: Int = 0,
-    userHandle: UserHandle = UserHandle.of(userId),
-    userInfo: UserInfo = mock(),
-    userProfiles: List<UserInfo> = emptyList(),
+    private var _userId: Int = 0,
+    private var _userHandle: UserHandle = UserHandle.of(_userId),
+    private var _userInfo: UserInfo = mock(),
+    private var _userProfiles: List<UserInfo> = emptyList(),
     userContentResolver: ContentResolver = MockContentResolver(),
     userContext: Context = mock(),
     private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
 ) : UserTracker {
     val callbacks = mutableListOf<UserTracker.Callback>()
 
-    override val userId: Int = userId
-    override val userHandle: UserHandle = userHandle
-    override val userInfo: UserInfo = userInfo
-    override val userProfiles: List<UserInfo> = userProfiles
+    override val userId: Int
+        get() = _userId
+    override val userHandle: UserHandle
+        get() = _userHandle
+    override val userInfo: UserInfo
+        get() = _userInfo
+    override val userProfiles: List<UserInfo>
+        get() = _userProfiles
 
     override val userContentResolver: ContentResolver = userContentResolver
     override val userContext: Context = userContext
@@ -55,4 +59,13 @@
     override fun createCurrentUserContext(context: Context): Context {
         return onCreateCurrentUserContext(context)
     }
+
+    fun set(userInfos: List<UserInfo>, selectedUserIndex: Int) {
+        _userProfiles = userInfos
+        _userInfo = userInfos[selectedUserIndex]
+        _userId = _userInfo.id
+        _userHandle = UserHandle.of(_userId)
+
+        callbacks.forEach { it.onUserChanged(_userId, userContext) }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
new file mode 100644
index 0000000..59f24ef
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.telephony.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTelephonyRepository : TelephonyRepository {
+
+    private val _callState = MutableStateFlow(0)
+    override val callState: Flow<Int> = _callState.asStateFlow()
+
+    fun setCallState(value: Int) {
+        _callState.value = value
+    }
+}
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 20f1e36..4df8aa4 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
@@ -17,12 +17,18 @@
 
 package com.android.systemui.user.data.repository
 
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+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 {
 
@@ -34,21 +40,71 @@
     private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
     override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
 
+    private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
+    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+        _userSwitcherSettings.asStateFlow()
+
+    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 var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+
     private val _isActionableWhenLocked = MutableStateFlow(false)
     override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
 
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
         get() = _isGuestUserAutoCreated
-    private var _isGuestUserResetting: Boolean = false
-    override val isGuestUserResetting: Boolean
-        get() = _isGuestUserResetting
+
+    override var isGuestUserResetting: Boolean = false
+
+    override val isGuestUserCreationScheduled = AtomicBoolean()
+
+    override var secondaryUserId: Int = UserHandle.USER_NULL
+
+    override var isRefreshUsersPaused: Boolean = false
+
+    var refreshUsersCallCount: Int = 0
+        private set
+
+    override fun refreshUsers() {
+        refreshUsersCallCount++
+    }
+
+    override fun getSelectedUserInfo(): UserInfo {
+        return checkNotNull(_selectedUserInfo.value)
+    }
+
+    override fun isSimpleUserSwitcher(): Boolean {
+        return _userSwitcherSettings.value.isSimpleUserSwitcher
+    }
+
+    fun setUserInfos(infos: List<UserInfo>) {
+        _userInfos.value = infos
+    }
+
+    suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+        check(_userInfos.value.contains(userInfo)) {
+            "Cannot select the following user, it is not in the list of user infos: $userInfo!"
+        }
+
+        _selectedUserInfo.value = userInfo
+        yield()
+    }
+
+    suspend fun setSettings(settings: UserSwitcherSettingsModel) {
+        _userSwitcherSettings.value = settings
+        yield()
+    }
 
     fun setUsers(models: List<UserModel>) {
         _users.value = models
     }
 
-    fun setSelectedUser(userId: Int) {
+    suspend fun setSelectedUser(userId: Int) {
         check(_users.value.find { it.id == userId } != null) {
             "Cannot select a user with ID $userId - no user with that ID found!"
         }
@@ -62,6 +118,7 @@
                 }
             }
         )
+        yield()
     }
 
     fun setActions(models: List<UserActionModel>) {
@@ -75,8 +132,4 @@
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
-
-    fun setGuestUserResetting(value: Boolean) {
-        _isGuestUserResetting = value
-    }
 }
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 2be67ed..23c7a61 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
@@ -70,6 +70,10 @@
     }
 
     @Override
+    public void setNewMobileIconSubIds(List<Integer> subIds) {
+    }
+
+    @Override
     public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
     }
 
diff --git a/packages/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml
index a63f592..f86a5d6 100644
--- a/packages/VpnDialogs/res/values-ro/strings.xml
+++ b/packages/VpnDialogs/res/values-ro/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"Solicitare de conexiune"</string>
     <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> dorește să configureze o conexiune VPN care să îi permită să monitorizeze traficul în rețea. Acceptă numai dacă ai încredere în sursă. Când conexiunea VPN e activă, &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; se afișează în partea de sus a ecranului."</string>
-    <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> solicită permisiunea de a configura o conexiune VPN care să îi permită să monitorizeze traficul de rețea. Acceptați numai dacă aveți încredere în sursă. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; va apărea pe ecran atunci când conexiunea VPN este activă."</string>
+    <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> solicită permisiunea de a configura o conexiune VPN care să îi permită să monitorizeze traficul de rețea. Acceptă numai dacă ai încredere în sursă. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; va apărea pe ecran când conexiunea VPN e activă."</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN este conectat"</string>
     <string name="session" msgid="6470628549473641030">"Sesiune:"</string>
     <string name="duration" msgid="3584782459928719435">"Durată:"</string>
@@ -26,10 +26,10 @@
     <string name="data_received" msgid="4062776929376067820">"Primite:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g>   byți/<xliff:g id="NUMBER_1">%2$s</xliff:g>   pachete"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"Nu se poate conecta la rețeaua VPN activată permanent"</string>
-    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Telefonul dvs. va folosi o rețea publică până când se va putea reconecta la <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> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Nu veți avea conexiune până când se va putea reconecta rețeaua VPN."</string>
+    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Telefonul va folosi o rețea publică până când se va putea reconecta la <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> este setată să rămână conectată permanent, dar momentan nu se poate conecta. Nu vei avea conexiune până când se va putea reconecta rețeaua VPN."</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
-    <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Modificați setările VPN"</string>
+    <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Modifică setările VPN"</string>
     <string name="configure" msgid="4905518375574791375">"Configurează"</string>
     <string name="disconnect" msgid="971412338304200056">"Deconectează"</string>
     <string name="open_app" msgid="3717639178595958667">"Deschide aplicația"</string>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml
index 6e5947c..b9cc0b0 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/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="3814493834951357513">"Redați aplicațiile sub zona de decupaj"</string>
+    <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Redă aplicațiile sub zona de decupaj"</string>
 </resources>
diff --git a/services/Android.bp b/services/Android.bp
index decfa77..637c4ee 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -89,11 +89,11 @@
         ":services.autofill-sources",
         ":services.backup-sources",
         ":backuplib-sources",
-        ":services.cloudsearch-sources",
         ":services.companion-sources",
         ":services.contentcapture-sources",
         ":services.contentsuggestions-sources",
         ":services.coverage-sources",
+        ":services.credentials-sources",
         ":services.devicepolicy-sources",
         ":services.midi-sources",
         ":services.musicsearch-sources",
@@ -143,11 +143,11 @@
         "services.appwidget",
         "services.autofill",
         "services.backup",
-        "services.cloudsearch",
         "services.companion",
         "services.contentcapture",
         "services.contentsuggestions",
         "services.coverage",
+        "services.credentials",
         "services.devicepolicy",
         "services.midi",
         "services.musicsearch",
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index 2b9f179..6e76a20 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -2,3 +2,5 @@
 sallyyuen@google.com
 ryanlwlin@google.com
 fuego@google.com
+danielnorman@google.com
+aarmaly@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 487703b..75724bf 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -38,7 +38,6 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.server.LocalServices;
-import com.android.server.accessibility.cursor.SoftwareCursorGestureHandler;
 import com.android.server.accessibility.gestures.TouchExplorer;
 import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
@@ -142,13 +141,6 @@
      */
     static final int FLAG_SEND_MOTION_EVENTS = 0x00000400;
 
-    /**
-     * Flag for enabling the Software Cursor accessibility feature.
-     *
-     * @see setUserAndEnabledFeatures(int, int)
-     */
-    static final int FLAG_FEATURE_SOFTWARE_CURSOR = 0x00000800;
-
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
@@ -157,8 +149,7 @@
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
                     | FLAG_REQUEST_MULTI_FINGER_GESTURES
-                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH
-                    | FLAG_FEATURE_SOFTWARE_CURSOR;
+                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
 
     private final Context mContext;
 
@@ -179,9 +170,6 @@
 
     private KeyboardInterceptor mKeyboardInterceptor;
 
-    private SparseArray<SoftwareCursorGestureHandler> mSoftwareCursorGestureHandler =
-            new SparseArray<>(0);
-
     private boolean mInstalled;
 
     private int mUserId;
@@ -507,16 +495,6 @@
             mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
         }
 
-        if ((mEnabledFeatures & FLAG_FEATURE_SOFTWARE_CURSOR) != 0) {
-            // TODO: Add full support for multiple displays.
-            final SoftwareCursorGestureHandler softwareCursorGestureHandler =
-                    new SoftwareCursorGestureHandler(displayContext,
-                        mAms.getSoftwareCursorManager(),
-                        mAms.getTraceManager());
-            addFirstEventHandler(displayId, softwareCursorGestureHandler);
-            mSoftwareCursorGestureHandler.put(displayId, softwareCursorGestureHandler);
-        }
-
         if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
             MotionEventInjector injector = new MotionEventInjector(
                     mContext.getMainLooper(), mAms.getTraceManager());
@@ -587,20 +565,12 @@
             mTouchExplorer.remove(displayId);
         }
 
-        final MagnificationGestureHandler handler =
-                mMagnificationGestureHandler.get(displayId);
+        final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
         if (handler != null) {
             handler.onDestroy();
             mMagnificationGestureHandler.remove(displayId);
         }
 
-        final SoftwareCursorGestureHandler softwareCursorHandler =
-                mSoftwareCursorGestureHandler.get(displayId);
-        if (softwareCursorHandler != null) {
-            softwareCursorHandler.onDestroy();
-            mSoftwareCursorGestureHandler.remove(displayId);
-        }
-
         final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId);
         if (eventStreamTransformation != null) {
             mEventHandler.remove(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8d94f95..1efbb0a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -49,6 +49,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
@@ -139,7 +140,6 @@
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.accessibility.cursor.SoftwareCursorManager;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
 import com.android.server.accessibility.magnification.MagnificationScaleProvider;
@@ -244,8 +244,6 @@
     private final MagnificationController mMagnificationController;
     private final MagnificationProcessor mMagnificationProcessor;
 
-    private final SoftwareCursorManager mSoftwareCursorManager;
-
     private final MainHandler mMainHandler;
 
     // Lazily initialized - access through getSystemActionPerformer()
@@ -359,6 +357,13 @@
                 EditorInfo editorInfo, boolean restarting) {
             mService.scheduleStartInput(remoteAccessibilityInputConnection, editorInfo, restarting);
         }
+
+        @Override
+        public boolean isTouchExplorationEnabled(@UserIdInt int userId) {
+            synchronized (mService.mLock) {
+                return mService.getUserStateLocked(userId).isTouchExplorationEnabledLocked();
+            }
+        }
     }
 
     public static final class Lifecycle extends SystemService {
@@ -407,7 +412,6 @@
         mMagnificationController = magnificationController;
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mSoftwareCursorManager = new SoftwareCursorManager();
         if (inputFilter != null) {
             mInputFilter = inputFilter;
             mHasInputFilter = true;
@@ -441,7 +445,6 @@
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mSoftwareCursorManager = new SoftwareCursorManager();
         init();
     }
 
@@ -731,7 +734,6 @@
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        intentFilter.addAction(Intent.ACTION_USER_PRESENT);
         intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
 
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
@@ -749,14 +751,6 @@
                     unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                    // We will update when the automation service dies.
-                    synchronized (mLock) {
-                        AccessibilityUserState userState = getCurrentUserStateLocked();
-                        if (readConfigurationForUserStateLocked(userState)) {
-                            onUserStateChangedLocked(userState);
-                        }
-                    }
                 } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
                     final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
                     if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
@@ -2288,9 +2282,6 @@
             if (userState.isPerformGesturesEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
             }
-            if (userState.isSoftwareCursorEnabledLocked()) {
-                flags |= AccessibilityInputFilter.FLAG_FEATURE_SOFTWARE_CURSOR;
-            }
             if (flags != 0) {
                 if (!mHasInputFilter) {
                     mHasInputFilter = true;
@@ -3492,15 +3483,6 @@
         return mMagnificationController;
     }
 
-    /**
-     * Getter of {@link SoftwareCursorManager}.
-     *
-     * @return SoftwareCursorManager
-     */
-    SoftwareCursorManager getSoftwareCursorManager() {
-        return mSoftwareCursorManager;
-    }
-
     @Override
     public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 5366a45..55dc196 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,7 +109,6 @@
     private boolean mIsAudioDescriptionByDefaultRequested;
     private boolean mIsAutoclickEnabled;
     private boolean mIsDisplayMagnificationEnabled;
-    private boolean mIsSoftwareCursorEnabled;
     private boolean mIsFilterKeyEventsEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -209,7 +208,6 @@
         mRequestTwoFingerPassthrough = false;
         mSendMotionEventsEnabled = false;
         mIsDisplayMagnificationEnabled = false;
-        mIsSoftwareCursorEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -511,8 +509,6 @@
         pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
-        pw.append(", softwareCursorEnabled=").append(String.valueOf(
-                mIsSoftwareCursorEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
         pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
         pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -623,14 +619,6 @@
         mIsDisplayMagnificationEnabled = enabled;
     }
 
-    public boolean isSoftwareCursorEnabledLocked() {
-        return mIsSoftwareCursorEnabled;
-    }
-
-    public void setSoftwareCursorEnabledLocked(boolean enabled) {
-        mIsSoftwareCursorEnabled = enabled;
-    }
-
     public boolean isFilterKeyEventsEnabledLocked() {
         return mIsFilterKeyEventsEnabled;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java
deleted file mode 100644
index 6ffa8f7..0000000
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java
+++ /dev/null
@@ -1,71 +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.accessibility.cursor;
-
-import android.content.Context;
-import android.util.Log;
-import android.util.Slog;
-import android.view.MotionEvent;
-
-import com.android.server.accessibility.AccessibilityTraceManager;
-import com.android.server.accessibility.BaseEventStreamTransformation;
-
-/**
- * Handles touch input for the Software Cursor accessibility feature.
- *
- * The behavior is as follows:
- *
- * <ol>
- *   <li> 1. Enable Software Cursor by swiping from the trigger zone on the edge of the screen.
- *   <li> 2. Move the cursor by swiping anywhere on the touch screen. Select by tapping anywhere on
- *   the touch screen.
- *   <li> 3. Put the cursor away by swiping it past either edge of the screen.
- * </ol>
- *
- * TODO(b/243552818): Determine how to handle multi-display.
- */
-public final class SoftwareCursorGestureHandler extends BaseEventStreamTransformation {
-
-
-    private static final String LOG_TAG = "SWCursorGestureHandler";
-    private static final boolean DEBUG_ALL = Log.isLoggable("SWCursorGestureHandler",
-            Log.DEBUG);
-
-    Context mContext;
-    SoftwareCursorManager mSoftwareCursorManager;
-    AccessibilityTraceManager mTraceManager;
-
-    public SoftwareCursorGestureHandler(Context context,
-                          SoftwareCursorManager softwareCursorManager,
-                          AccessibilityTraceManager traceManager) {
-        mContext = context;
-        mSoftwareCursorManager = softwareCursorManager;
-        mTraceManager = traceManager;
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (DEBUG_ALL) {
-            Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
-        }
-        // TODO: Add logic.
-        // TODO: Prevent users from enabling this filter in conjuntion with TouchExplorer.
-        super.onMotionEvent(event, rawEvent, policyFlags);
-    }
-
-
-}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5a35474..cc29109 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -770,12 +770,14 @@
     /**
      * Updates the last fill selection when an authentication was selected.
      */
-    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
+    void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
+            int uiType) {
         synchronized (mLock) {
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
-                                null, null, null, null, null, null));
+                                null, null, null, null, null, null,
+                                NO_SAVE_UI_REASON_NONE, uiType));
             }
         }
     }
@@ -784,12 +786,13 @@
      * Updates the last fill selection when an dataset authentication was selected.
      */
     void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState) {
+            @Nullable Bundle clientState, int uiType) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState, null, null, null, null, null, null, null, null));
+                                clientState, null, null, null, null, null, null, null, null,
+                                NO_SAVE_UI_REASON_NONE, uiType));
             }
         }
     }
@@ -810,13 +813,13 @@
      * Updates the last fill response when a dataset was selected.
      */
     void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState,  int presentationType) {
+            @Nullable Bundle clientState,  int uiType) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
                                 null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                presentationType));
+                                uiType));
             }
         }
     }
@@ -824,13 +827,13 @@
     /**
      * Updates the last fill response when a dataset is shown.
      */
-    void logDatasetShown(int sessionId, @Nullable Bundle clientState, int presentationType) {
+    void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetShown", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
                                 null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                presentationType));
+                                uiType));
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index b5fdaca..6bb19ce 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -32,6 +32,8 @@
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
@@ -72,19 +74,32 @@
             NOT_SHOWN_REASON_VIEW_CHANGED,
             NOT_SHOWN_REASON_ACTIVITY_FINISHED,
             NOT_SHOWN_REASON_REQUEST_TIMEOUT,
+            NOT_SHOWN_REASON_REQUEST_FAILED,
+            NOT_SHOWN_REASON_NO_FOCUS,
             NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY,
             NOT_SHOWN_REASON_UNKNOWN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NotShownReason {}
 
-    public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
-    public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
-    public static final int NOT_SHOWN_REASON_VIEW_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
-    public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
-    public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
-    public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
-    public static final int NOT_SHOWN_REASON_UNKNOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
+    public static final int NOT_SHOWN_REASON_ANY_SHOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
+    public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
+    public static final int NOT_SHOWN_REASON_VIEW_CHANGED =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
+    public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
+    public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT;
+    public static final int NOT_SHOWN_REASON_REQUEST_FAILED =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED;
+    public static final int NOT_SHOWN_REASON_NO_FOCUS =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS;
+    public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
+    public static final int NOT_SHOWN_REASON_UNKNOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
 
     private final int mSessionId;
     private Optional<PresentationStatsEventInternal> mEventInternal;
@@ -118,6 +133,14 @@
         });
     }
 
+    public void maybeSetNoPresentationEventReasonIfNoReasonExists(@NotShownReason int reason) {
+        mEventInternal.ifPresent(event -> {
+            if (event.mCountShown == 0 && event.mNoPresentationReason == NOT_SHOWN_REASON_UNKNOWN) {
+                event.mNoPresentationReason = reason;
+            }
+        });
+    }
+
     public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList,
             AutofillId currentViewId) {
         mEventInternal.ifPresent(event -> {
@@ -180,7 +203,8 @@
 
     public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
         mEventInternal.ifPresent(event -> {
-            event.mDisplayPresentationType = UI_TYPE_INLINE;
+            event.mDisplayPresentationType =
+                AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
             String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
                     Settings.Secure.DEFAULT_INPUT_METHOD, userId);
             if (TextUtils.isEmpty(imeString)) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d5945a5..47ce592 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -44,6 +44,8 @@
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_CHANGED;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED;
@@ -407,6 +409,13 @@
     @GuardedBy("mLock")
     private PresentationStatsEventLogger mPresentationStatsEventLogger;
 
+    /**
+     * Fill dialog request would likely be sent slightly later.
+     */
+    @NonNull
+    @GuardedBy("mLock")
+    private boolean mPreviouslyFillDialogPotentiallyStarted;
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -1179,6 +1188,7 @@
     @Override
     public void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
             @NonNull String servicePackageName, int requestFlags) {
+
         final AutofillId[] fieldClassificationIds;
 
         final LogMaker requestLog;
@@ -1357,9 +1367,13 @@
                 }
             }
 
-            // TODO(b/234185326): Add separate reason for failures.
-            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                    NOT_SHOWN_REASON_REQUEST_TIMEOUT);
+            if (timedOut) {
+                mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                        NOT_SHOWN_REASON_REQUEST_TIMEOUT);
+            } else {
+                mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                        NOT_SHOWN_REASON_REQUEST_FAILED);
+            }
             mPresentationStatsEventLogger.logAndEndEvent();
         }
         notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED,
@@ -1468,7 +1482,7 @@
     // AutoFillUiCallback
     @Override
     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras,
-            boolean authenticateInline) {
+            int uiType) {
         if (sDebug) {
             Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
                     + "; intentSender=" + intent);
@@ -1487,12 +1501,13 @@
             }
         }
 
-        mService.setAuthenticationSelected(id, mClientState);
+        mService.setAuthenticationSelected(id, mClientState, uiType);
 
         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
         mHandler.sendMessage(obtainMessage(
                 Session::startAuthentication,
-                this, authenticationId, intent, fillInIntent, authenticateInline));
+                this, authenticationId, intent, fillInIntent,
+                /* authenticateInline= */ uiType == UI_TYPE_INLINE));
     }
 
     // AutoFillUiCallback
@@ -2887,6 +2902,7 @@
     @GuardedBy("mLock")
     private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
             @NonNull ViewState viewState, int flags) {
+        // Force new response for manual request
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mSessionFlags.mAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
@@ -2894,7 +2910,7 @@
             return true;
         }
 
-        // If it's not, then check if it it should start a partition.
+        // If it's not, then check if it should start a partition.
         if (shouldStartNewPartitionLocked(id)) {
             if (sDebug) {
                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
@@ -2993,7 +3009,7 @@
             if (sDebug) {
                 Slog.d(TAG, "Set the response has expired.");
             }
-            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+            mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
                         NOT_SHOWN_REASON_VIEW_CHANGED);
             mPresentationStatsEventLogger.logAndEndEvent();
             return;
@@ -3038,11 +3054,20 @@
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
-                if (!isRequestSupportFillDialog(flags)) {
-                    mSessionFlags.mFillDialogDisabled = true;
-                }
                 mPresentationStatsEventLogger.startNewEvent();
                 mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+                mPresentationStatsEventLogger.maybeSetIsNewRequest(true);
+                if (!isRequestSupportFillDialog(flags)) {
+                    mSessionFlags.mFillDialogDisabled = true;
+                    mPreviouslyFillDialogPotentiallyStarted = false;
+                } else {
+                    // Set the default reason for now if the user doesn't trigger any focus event
+                    // on the autofillable view. This can be changed downstream when more
+                    // information is available or session is committed.
+                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                            NOT_SHOWN_REASON_NO_FOCUS);
+                    mPreviouslyFillDialogPotentiallyStarted = true;
+                }
                 requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
                 break;
             case ACTION_VALUE_CHANGED:
@@ -3085,6 +3110,8 @@
                 }
                 break;
             case ACTION_VIEW_ENTERED:
+                boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted;
+                mPreviouslyFillDialogPotentiallyStarted = false;
                 if (sVerbose && virtualBounds != null) {
                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                 }
@@ -3101,9 +3128,15 @@
                     return;
                 }
 
-                mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                        NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
-                mPresentationStatsEventLogger.logAndEndEvent();
+                // Previously, fill request will only start whenever a view is entered.
+                // With Fill Dialog, request starts prior to view getting entered. So, we can't end
+                // the event at this moment, otherwise we will be wrongly attributing fill dialog
+                // event as concluded.
+                if (!wasPreviouslyFillDialog) {
+                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                            NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
+                    mPresentationStatsEventLogger.logAndEndEvent();
+                }
 
                 if ((flags & FLAG_MANUAL_REQUEST) == 0) {
                     // Not a manual request
@@ -3131,9 +3164,22 @@
                     }
                 }
 
-                mPresentationStatsEventLogger.startNewEvent();
-                mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+                if (!wasPreviouslyFillDialog) {
+                    mPresentationStatsEventLogger.startNewEvent();
+                    mPresentationStatsEventLogger.maybeSetAutofillServiceUid(
+                            getAutofillServiceUid());
+                }
                 if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
+                    // If a new request was issued even if previously it was fill dialog request,
+                    // we should end the log event, and start a new one. However, it leaves us
+                    // susceptible to race condition. But since mPresentationStatsEventLogger is
+                    // lock guarded, we should be safe.
+                    if (wasPreviouslyFillDialog) {
+                        mPresentationStatsEventLogger.logAndEndEvent();
+                        mPresentationStatsEventLogger.startNewEvent();
+                        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(
+                                getAutofillServiceUid());
+                    }
                     return;
                 }
 
@@ -3369,7 +3415,7 @@
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
                     final ViewState currentView = mViewStates.get(mCurrentViewId);
                     currentView.setState(ViewState.STATE_INLINE_SHOWN);
-                    // TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
+                    // TODO(b/248378401): Fix it to log showed only when IME asks for inflation,
                     // rather than here where framework sends back the response.
                     mService.logDatasetShown(id, mClientState, UI_TYPE_INLINE);
 
@@ -3390,7 +3436,6 @@
 
         synchronized (mLock) {
             mService.logDatasetShown(id, mClientState, UI_TYPE_MENU);
-
             mPresentationStatsEventLogger.maybeSetCountShown(
                     response.getDatasets(), mCurrentViewId);
             mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU);
@@ -3547,7 +3592,7 @@
                     public void authenticate(int requestId, int datasetIndex) {
                         Session.this.authenticate(response.getRequestId(), datasetIndex,
                                 response.getAuthentication(), response.getClientState(),
-                                /* authenticateInline= */ true);
+                                UI_TYPE_INLINE);
                     }
 
                     @Override
@@ -4087,7 +4132,7 @@
             }
 
             // ...or handle authentication.
-            mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
+            mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType);
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
             if (fillInIntent == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index e07f412..5f0f9a3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -84,7 +84,7 @@
 
     public interface AutoFillUiCallback {
         void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent,
-                @Nullable Bundle extras, boolean authenticateInline);
+                @Nullable Bundle extras, int uiType);
         void fill(int requestId, int datasetIndex, @NonNull Dataset dataset,
                 @FillEventHistory.Event.UiType int uiType);
         void save();
@@ -232,7 +232,7 @@
                         mCallback.authenticate(response.getRequestId(),
                                 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
                                 response.getAuthentication(), response.getClientState(),
-                                /* authenticateInline= */ false);
+                                UI_TYPE_MENU);
                     }
                 }
 
@@ -419,7 +419,7 @@
                                 mCallback.authenticate(response.getRequestId(),
                                         AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
                                         response.getAuthentication(), response.getClientState(),
-                                        /* authenticateInline= */ false);
+                                        UI_TYPE_DIALOG);
                             }
                         }
 
diff --git a/services/cloudsearch/Android.bp b/services/cloudsearch/Android.bp
deleted file mode 100644
index e38e615..0000000
--- a/services/cloudsearch/Android.bp
+++ /dev/null
@@ -1,22 +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"],
-}
-
-filegroup {
-    name: "services.cloudsearch-sources",
-    srcs: ["java/**/*.java"],
-    path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library_static {
-    name: "services.cloudsearch",
-    defaults: ["platform_service_defaults"],
-    srcs: [":services.cloudsearch-sources"],
-    libs: ["services.core"],
-}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
deleted file mode 100644
index ac2d1dd..0000000
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
+++ /dev/null
@@ -1,202 +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.cloudsearch;
-
-import static android.Manifest.permission.MANAGE_CLOUDSEARCH;
-import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.content.Context.CLOUDSEARCH_SERVICE;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
-import android.app.cloudsearch.ICloudSearchManager;
-import android.app.cloudsearch.ICloudSearchManagerCallback;
-import android.app.cloudsearch.SearchRequest;
-import android.app.cloudsearch.SearchResponse;
-import android.content.Context;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.infra.AbstractMasterSystemService;
-import com.android.server.infra.FrameworkResourcesServiceNameResolver;
-import com.android.server.wm.ActivityTaskManagerInternal;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * A service used to return cloudsearch targets given a query.
- */
-public class CloudSearchManagerService extends
-        AbstractMasterSystemService<CloudSearchManagerService, CloudSearchPerUserService> {
-
-    private static final String TAG = CloudSearchManagerService.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
-
-    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
-
-    private final Context mContext;
-
-    public CloudSearchManagerService(Context context) {
-        super(context, new FrameworkResourcesServiceNameResolver(context,
-                        R.array.config_defaultCloudSearchServices, true), null,
-                PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
-        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
-        mContext = context;
-    }
-
-    @Override
-    protected CloudSearchPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
-        return new CloudSearchPerUserService(this, mLock, resolvedUserId, "");
-    }
-
-    @Override
-    protected List<CloudSearchPerUserService> newServiceListLocked(int resolvedUserId,
-            boolean disabled, String[] serviceNames) {
-        if (serviceNames == null) {
-            return new ArrayList<>();
-        }
-        List<CloudSearchPerUserService> serviceList =
-                new ArrayList<>(serviceNames.length);
-        for (int i = 0; i < serviceNames.length; i++) {
-            if (serviceNames[i] == null) {
-                continue;
-            }
-            serviceList.add(new CloudSearchPerUserService(this, mLock, resolvedUserId,
-                    serviceNames[i]));
-        }
-        return serviceList;
-    }
-
-    @Override
-    public void onStart() {
-        publishBinderService(CLOUDSEARCH_SERVICE, new CloudSearchManagerStub());
-    }
-
-    @Override
-    protected void enforceCallingPermissionForManagement() {
-        getContext().enforceCallingPermission(MANAGE_CLOUDSEARCH, TAG);
-    }
-
-    @Override // from AbstractMasterSystemService
-    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
-        final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
-        if (service != null) {
-            service.onPackageUpdatedLocked();
-        }
-    }
-
-    @Override // from AbstractMasterSystemService
-    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
-        final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
-        if (service != null) {
-            service.onPackageRestartedLocked();
-        }
-    }
-
-    @Override
-    protected int getMaximumTemporaryServiceDurationMs() {
-        return MAX_TEMP_SERVICE_DURATION_MS;
-    }
-
-    private class CloudSearchManagerStub extends ICloudSearchManager.Stub {
-
-        @Override
-        public void search(@NonNull SearchRequest searchRequest,
-                @NonNull ICloudSearchManagerCallback callBack) {
-            searchRequest.setCallerPackageName(
-                    mContext.getPackageManager().getNameForUid(Binder.getCallingUid()));
-            runForUser("search", (service) -> {
-                synchronized (service.mLock) {
-                    service.onSearchLocked(searchRequest, callBack);
-                }
-            });
-        }
-
-        @Override
-        public void returnResults(IBinder token, String requestId, SearchResponse response) {
-            runForUser("returnResults", (service) -> {
-                synchronized (service.mLock) {
-                    service.onReturnResultsLocked(token, requestId, response);
-                }
-            });
-        }
-
-        public void destroy(@NonNull SearchRequest searchRequest) {
-            runForUser("destroyCloudSearchSession", (service) -> {
-                synchronized (service.mLock) {
-                    service.onDestroyLocked(searchRequest.getRequestId());
-                }
-            });
-        }
-
-        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
-                @Nullable FileDescriptor err,
-                @NonNull String[] args, @Nullable ShellCallback callback,
-                @NonNull ResultReceiver resultReceiver) {
-            new CloudSearchManagerServiceShellCommand(CloudSearchManagerService.this)
-                    .exec(this, in, out, err, args, callback, resultReceiver);
-        }
-
-        private void runForUser(@NonNull final String func,
-                @NonNull final Consumer<CloudSearchPerUserService> c) {
-            ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
-            final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                    Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
-                    null, null);
-
-            if (DEBUG) {
-                Slog.d(TAG, "runForUser:" + func + " from pid=" + Binder.getCallingPid()
-                        + ", uid=" + Binder.getCallingUid());
-            }
-            Context ctx = getContext();
-            if (!(ctx.checkCallingPermission(MANAGE_CLOUDSEARCH) == PERMISSION_GRANTED
-                    || mServiceNameResolver.isTemporary(userId)
-                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
-
-                String msg = "Permission Denial: Cannot call " + func + " from pid="
-                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    final List<CloudSearchPerUserService> services =
-                            getServiceListForUserLocked(userId);
-                    for (int i = 0; i < services.size(); i++) {
-                        c.accept(services.get(i));
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
-    }
-}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java
deleted file mode 100644
index c64982d..0000000
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java
+++ /dev/null
@@ -1,89 +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.cloudsearch;
-
-import android.annotation.NonNull;
-import android.os.ShellCommand;
-
-import java.io.PrintWriter;
-
-/**
- * The shell command implementation for the CloudSearchManagerService.
- */
-public class CloudSearchManagerServiceShellCommand extends ShellCommand {
-
-    private static final String TAG =
-            CloudSearchManagerServiceShellCommand.class.getSimpleName();
-
-    private final CloudSearchManagerService mService;
-
-    public CloudSearchManagerServiceShellCommand(@NonNull CloudSearchManagerService service) {
-        mService = service;
-    }
-
-    @Override
-    public int onCommand(String cmd) {
-        if (cmd == null) {
-            return handleDefaultCommands(cmd);
-        }
-        final PrintWriter pw = getOutPrintWriter();
-        switch (cmd) {
-            case "set": {
-                final String what = getNextArgRequired();
-                switch (what) {
-                    case "temporary-service": {
-                        final int userId = Integer.parseInt(getNextArgRequired());
-                        String serviceName = getNextArg();
-                        if (serviceName == null) {
-                            mService.resetTemporaryService(userId);
-                            pw.println("CloudSearchService temporarily reset. ");
-                            return 0;
-                        }
-                        final int duration = Integer.parseInt(getNextArgRequired());
-                        String[] services = serviceName.split(";");
-                        if (services.length == 0) {
-                            return 0;
-                        } else {
-                            mService.setTemporaryServices(userId, services, duration);
-                        }
-                        pw.println("CloudSearchService temporarily set to " + serviceName
-                                + " for " + duration + "ms");
-                        break;
-                    }
-                }
-            }
-            break;
-            default:
-                return handleDefaultCommands(cmd);
-        }
-        return 0;
-    }
-
-    @Override
-    public void onHelp() {
-        try (PrintWriter pw = getOutPrintWriter()) {
-            pw.println("CloudSearchManagerService commands:");
-            pw.println("  help");
-            pw.println("    Prints this help text.");
-            pw.println("");
-            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
-            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
-            pw.println("    To reset, call with just the USER_ID argument.");
-            pw.println("");
-        }
-    }
-}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
deleted file mode 100644
index 222d779..0000000
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
+++ /dev/null
@@ -1,401 +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.cloudsearch;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppGlobals;
-import android.app.cloudsearch.ICloudSearchManagerCallback;
-import android.app.cloudsearch.SearchRequest;
-import android.app.cloudsearch.SearchResponse;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.service.cloudsearch.CloudSearchService;
-import android.service.cloudsearch.ICloudSearchService;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AbstractRemoteService;
-import com.android.server.CircularQueue;
-import com.android.server.infra.AbstractPerUserSystemService;
-
-/**
- * Per-user instance of {@link CloudSearchManagerService}.
- */
-public class CloudSearchPerUserService extends
-        AbstractPerUserSystemService<CloudSearchPerUserService, CloudSearchManagerService>
-        implements RemoteCloudSearchService.RemoteCloudSearchServiceCallbacks {
-
-    private static final String TAG = CloudSearchPerUserService.class.getSimpleName();
-    private static final int QUEUE_SIZE = 10;
-    @GuardedBy("mLock")
-    private final CircularQueue<String, CloudSearchCallbackInfo> mCallbackQueue =
-            new CircularQueue<>(QUEUE_SIZE);
-    private final String mServiceName;
-    private final ComponentName mRemoteComponentName;
-    @Nullable
-    @GuardedBy("mLock")
-    private RemoteCloudSearchService mRemoteService;
-    /**
-     * When {@code true}, remote service died but service state is kept so it's restored after
-     * the system re-binds to it.
-     */
-    @GuardedBy("mLock")
-    private boolean mZombie;
-
-    protected CloudSearchPerUserService(CloudSearchManagerService master,
-            Object lock, int userId, String serviceName) {
-        super(master, lock, userId);
-        mServiceName = serviceName;
-        mRemoteComponentName = ComponentName.unflattenFromString(mServiceName);
-    }
-
-    @Override // from PerUserSystemService
-    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
-            throws NameNotFoundException {
-
-        ServiceInfo si;
-        try {
-            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
-                    PackageManager.GET_META_DATA, mUserId);
-        } catch (RemoteException e) {
-            throw new NameNotFoundException("Could not get service for " + serviceComponent);
-        }
-        // TODO(b/177858728): must check that either the service is from a system component,
-        // or it matches a service set by shell cmd (so it can be used on CTS tests and when
-        // OEMs are implementing the real service and also verify the proper permissions
-        return si;
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    protected boolean updateLocked(boolean disabled) {
-        final boolean enabledChanged = super.updateLocked(disabled);
-        if (enabledChanged) {
-            if (isEnabledLocked()) {
-                // Send the pending sessions over to the service
-                resurrectSessionsLocked();
-            } else {
-                // Clear the remote service for the next call
-                updateRemoteServiceLocked();
-            }
-        }
-        return enabledChanged;
-    }
-
-    /**
-     * Notifies the service of a new cloudsearch session.
-     */
-    @GuardedBy("mLock")
-    public void onSearchLocked(@NonNull SearchRequest searchRequest,
-            @NonNull ICloudSearchManagerCallback callback) {
-        if (mRemoteComponentName == null) {
-            return;
-        }
-
-        String filterList = searchRequest.getSearchConstraints().containsKey(
-                SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER)
-                ? searchRequest.getSearchConstraints().getString(
-                SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER) : "";
-
-        String remoteServicePackageName = mRemoteComponentName.getPackageName();
-        // By default, all providers are marked as wanted.
-        boolean wantedProvider = true;
-        if (filterList.length() > 0) {
-            // If providers are specified by the client,
-            wantedProvider = false;
-            String[] providersSpecified = filterList.split(";");
-            for (int i = 0; i < providersSpecified.length; i++) {
-                if (providersSpecified[i].equals(remoteServicePackageName)) {
-                    wantedProvider = true;
-                    break;
-                }
-            }
-        }
-        // If the provider was not requested by the Client, the request will not be sent to the
-        // provider.
-        if (!wantedProvider) {
-            // TODO(216520546) Send a failure callback to the client.
-            return;
-        }
-        final boolean serviceExists = resolveService(searchRequest,
-                s -> s.onSearch(searchRequest));
-        String requestId = searchRequest.getRequestId();
-        if (serviceExists && !mCallbackQueue.containsKey(requestId)) {
-            final CloudSearchCallbackInfo sessionInfo = new CloudSearchCallbackInfo(
-                    requestId, searchRequest, callback, callback.asBinder(), () -> {
-                synchronized (mLock) {
-                    onDestroyLocked(requestId);
-                }
-            });
-            if (sessionInfo.linkToDeath()) {
-                CloudSearchCallbackInfo removedInfo = mCallbackQueue.put(requestId, sessionInfo);
-                if (removedInfo != null) {
-                    removedInfo.destroy();
-                }
-            } else {
-                // destroy the session if calling process is already dead
-                onDestroyLocked(requestId);
-            }
-        }
-    }
-
-    /**
-     * Used to return results back to the clients.
-     */
-    @GuardedBy("mLock")
-    public void onReturnResultsLocked(@NonNull IBinder token,
-            @NonNull String requestId,
-            @NonNull SearchResponse response) {
-        if (mRemoteService == null) {
-            return;
-        }
-        ICloudSearchService serviceInterface = mRemoteService.getServiceInterface();
-        if (serviceInterface == null || token != serviceInterface.asBinder()) {
-            return;
-        }
-        if (mCallbackQueue.containsKey(requestId)) {
-            response.setSource(mServiceName);
-            final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.getElement(requestId);
-            try {
-                if (response.getStatusCode() == SearchResponse.SEARCH_STATUS_OK) {
-                    sessionInfo.mCallback.onSearchSucceeded(response);
-                } else {
-                    sessionInfo.mCallback.onSearchFailed(response);
-                }
-            } catch (RemoteException e) {
-                if (mMaster.debug) {
-                    Slog.e(TAG, "Exception in posting results");
-                    e.printStackTrace();
-                }
-                onDestroyLocked(requestId);
-            }
-        }
-    }
-
-    /**
-     * Notifies the server about the end of an existing cloudsearch session.
-     */
-    @GuardedBy("mLock")
-    public void onDestroyLocked(@NonNull String requestId) {
-        if (isDebug()) {
-            Slog.d(TAG, "onDestroyLocked(): requestId=" + requestId);
-        }
-        final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.removeElement(requestId);
-        if (sessionInfo != null) {
-            sessionInfo.destroy();
-        }
-    }
-
-    @Override
-    public void onFailureOrTimeout(boolean timedOut) {
-        if (isDebug()) {
-            Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
-        }
-        // Do nothing, we are just proxying to the cloudsearch service
-    }
-
-    @Override
-    public void onConnectedStateChanged(boolean connected) {
-        if (isDebug()) {
-            Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
-        }
-        if (connected) {
-            synchronized (mLock) {
-                if (mZombie) {
-                    // Validation check - shouldn't happen
-                    if (mRemoteService == null) {
-                        Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
-                        return;
-                    }
-                    mZombie = false;
-                    resurrectSessionsLocked();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onServiceDied(RemoteCloudSearchService service) {
-        if (isDebug()) {
-            Slog.w(TAG, "onServiceDied(): service=" + service);
-        }
-        synchronized (mLock) {
-            mZombie = true;
-        }
-        updateRemoteServiceLocked();
-    }
-
-    @GuardedBy("mLock")
-    private void updateRemoteServiceLocked() {
-        if (mRemoteService != null) {
-            mRemoteService.destroy();
-            mRemoteService = null;
-        }
-    }
-
-    void onPackageUpdatedLocked() {
-        if (isDebug()) {
-            Slog.v(TAG, "onPackageUpdatedLocked()");
-        }
-        destroyAndRebindRemoteService();
-    }
-
-    void onPackageRestartedLocked() {
-        if (isDebug()) {
-            Slog.v(TAG, "onPackageRestartedLocked()");
-        }
-        destroyAndRebindRemoteService();
-    }
-
-    private void destroyAndRebindRemoteService() {
-        if (mRemoteService == null) {
-            return;
-        }
-
-        if (isDebug()) {
-            Slog.d(TAG, "Destroying the old remote service.");
-        }
-        mRemoteService.destroy();
-        mRemoteService = null;
-
-        synchronized (mLock) {
-            mZombie = true;
-        }
-        mRemoteService = getRemoteServiceLocked();
-        if (mRemoteService != null) {
-            if (isDebug()) {
-                Slog.d(TAG, "Rebinding to the new remote service.");
-            }
-            mRemoteService.reconnect();
-        }
-    }
-
-    /**
-     * Called after the remote service connected, it's used to restore state from a 'zombie'
-     * service (i.e., after it died).
-     */
-    private void resurrectSessionsLocked() {
-        final int numCallbacks = mCallbackQueue.size();
-        if (isDebug()) {
-            Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
-                    + numCallbacks + " requests.");
-        }
-
-        for (CloudSearchCallbackInfo callbackInfo : mCallbackQueue.values()) {
-            callbackInfo.resurrectSessionLocked(this, callbackInfo.mToken);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    protected boolean resolveService(
-            @NonNull final SearchRequest requestId,
-            @NonNull final AbstractRemoteService.AsyncRequest<ICloudSearchService> cb) {
-
-        final RemoteCloudSearchService service = getRemoteServiceLocked();
-        if (service != null) {
-            service.executeOnResolvedService(cb);
-        }
-        return service != null;
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteCloudSearchService getRemoteServiceLocked() {
-        if (mRemoteService == null) {
-            final String serviceName = getComponentNameForMultipleLocked(mServiceName);
-            if (serviceName == null) {
-                if (mMaster.verbose) {
-                    Slog.v(TAG, "getRemoteServiceLocked(): not set");
-                }
-                return null;
-            }
-            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-
-            mRemoteService = new RemoteCloudSearchService(getContext(),
-                    CloudSearchService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
-                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
-        }
-
-        return mRemoteService;
-    }
-
-    private static final class CloudSearchCallbackInfo {
-        private static final boolean DEBUG = false;  // Do not submit with true
-        @NonNull
-        final IBinder mToken;
-        @NonNull
-        final IBinder.DeathRecipient mDeathRecipient;
-        @NonNull
-        private final String mRequestId;
-        @NonNull
-        private final SearchRequest mSearchRequest;
-        private final ICloudSearchManagerCallback mCallback;
-
-        CloudSearchCallbackInfo(
-                @NonNull final String id,
-                @NonNull final SearchRequest request,
-                @NonNull final ICloudSearchManagerCallback callback,
-                @NonNull final IBinder token,
-                @NonNull final IBinder.DeathRecipient deathRecipient) {
-            if (DEBUG) {
-                Slog.d(TAG, "Creating CloudSearchSessionInfo for session Id=" + id);
-            }
-            mRequestId = id;
-            mSearchRequest = request;
-            mCallback = callback;
-            mToken = token;
-            mDeathRecipient = deathRecipient;
-        }
-
-        boolean linkToDeath() {
-            try {
-                mToken.linkToDeath(mDeathRecipient, 0);
-            } catch (RemoteException e) {
-                if (DEBUG) {
-                    Slog.w(TAG, "Caller is dead before session can be started, requestId: "
-                            + mRequestId);
-                }
-                return false;
-            }
-            return true;
-        }
-
-        void destroy() {
-            if (DEBUG) {
-                Slog.d(TAG, "Removing callback for Request Id=" + mRequestId);
-            }
-            if (mToken != null) {
-                mToken.unlinkToDeath(mDeathRecipient, 0);
-            }
-            mCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
-        }
-
-        void resurrectSessionLocked(CloudSearchPerUserService service, IBinder token) {
-            if (DEBUG) {
-                Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
-                        + ") for request Id=" + mRequestId);
-            }
-            service.onSearchLocked(mSearchRequest, mCallback);
-        }
-    }
-}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java b/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java
deleted file mode 100644
index d1c0482..0000000
--- a/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java
+++ /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.cloudsearch;
-
-import android.annotation.NonNull;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.IBinder;
-import android.service.cloudsearch.ICloudSearchService;
-import android.text.format.DateUtils;
-
-import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
-
-
-/**
- * Proxy to the {@link android.service.cloudsearch.CloudSearchService} implementation in another
- * process.
- */
-public class RemoteCloudSearchService extends
-        AbstractMultiplePendingRequestsRemoteService<RemoteCloudSearchService,
-                ICloudSearchService> {
-
-    private static final String TAG = "RemoteCloudSearchService";
-
-    private static final long TIMEOUT_IDLE_BOUND_TIMEOUT_MS = 10 * DateUtils.MINUTE_IN_MILLIS;
-    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
-
-    private final RemoteCloudSearchServiceCallbacks mCallback;
-
-    public RemoteCloudSearchService(Context context, String serviceInterface,
-            ComponentName componentName, int userId,
-            RemoteCloudSearchServiceCallbacks callback, boolean bindInstantServiceAllowed,
-            boolean verbose) {
-        super(context, serviceInterface, componentName, userId, callback,
-                context.getMainThreadHandler(),
-                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
-                verbose, /* initialCapacity= */ 1);
-        mCallback = callback;
-    }
-
-    @Override
-    protected ICloudSearchService getServiceInterface(IBinder service) {
-        return ICloudSearchService.Stub.asInterface(service);
-    }
-
-    @Override
-    protected long getTimeoutIdleBindMillis() {
-        return TIMEOUT_IDLE_BOUND_TIMEOUT_MS;
-    }
-
-    @Override
-    protected long getRemoteRequestMillis() {
-        return TIMEOUT_REMOTE_REQUEST_MILLIS;
-    }
-
-    /**
-     * Schedules a request to bind to the remote service.
-     */
-    public void reconnect() {
-        super.scheduleBind();
-    }
-
-    /**
-     * Schedule async request on remote service.
-     */
-    public void scheduleOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
-        scheduleAsyncRequest(request);
-    }
-
-    /**
-     * Execute async request on remote service immediately instead of sending it to Handler queue.
-     */
-    public void executeOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
-        executeAsyncRequest(request);
-    }
-
-    /**
-     * Failure callback
-     */
-    public interface RemoteCloudSearchServiceCallbacks
-            extends VultureCallback<RemoteCloudSearchService> {
-
-        /**
-         * Notifies a the failure or timeout of a remote call.
-         */
-        void onFailureOrTimeout(boolean timedOut);
-
-        /**
-         * Notifies change in connected state of the remote service.
-         */
-        void onConnectedStateChanged(boolean connected);
-    }
-
-    @Override // from AbstractRemoteService
-    protected void handleOnConnectedStateChanged(boolean connected) {
-        if (mCallback != null) {
-            mCallback.onConnectedStateChanged(connected);
-        }
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index dc9144a..0ab5a8a 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -116,6 +116,7 @@
     private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
     private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+    private static final String EXTRA_FORCE_CANCEL_CONFIRMATION = "cancel_confirmation";
 
     // AssociationRequestsProcessor -> UI
     private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
@@ -195,21 +196,7 @@
         intent.putExtras(extras);
 
         // 2b.3. Create a PendingIntent.
-        final PendingIntent pendingIntent;
-        final long token = Binder.clearCallingIdentity();
-        try {
-            // Using uid of the application that will own the association (usually the same
-            // application that sent the request) allows us to have multiple "pending" association
-            // requests at the same time.
-            // If the application already has a pending association request, that PendingIntent
-            // will be cancelled.
-            pendingIntent = PendingIntent.getActivityAsUser(
-                    mContext, /*requestCode */ packageUid, intent,
-                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
-                    /* options= */ null, UserHandle.CURRENT);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        final PendingIntent pendingIntent = createPendingIntent(packageUid, intent);
 
         // 2b.4. Send the PendingIntent back to the app.
         try {
@@ -217,6 +204,27 @@
         } catch (RemoteException ignore) { }
     }
 
+    /**
+     * Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog.
+     */
+    PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
+            @UserIdInt int userId) {
+        requireNonNull(packageName, "Package name MUST NOT be null");
+
+        enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
+
+        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+
+        final Bundle extras = new Bundle();
+        extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
+
+        final Intent intent = new Intent();
+        intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+        intent.putExtras(extras);
+
+        return createPendingIntent(packageUid, intent);
+    }
+
     private void processAssociationRequestApproval(@NonNull AssociationRequest request,
             @NonNull IAssociationRequestCallback callback,
             @NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) {
@@ -286,6 +294,27 @@
         return !isRoleHolder;
     }
 
+    private PendingIntent createPendingIntent(int packageUid, Intent intent) {
+        final PendingIntent pendingIntent;
+        final long token = Binder.clearCallingIdentity();
+
+        // Using uid of the application that will own the association (usually the same
+        // application that sent the request) allows us to have multiple "pending" association
+        // requests at the same time.
+        // If the application already has a pending association request, that PendingIntent
+        // will be cancelled except application wants to cancel the request by the system.
+        try {
+            pendingIntent = PendingIntent.getActivityAsUser(
+                    mContext, /*requestCode */ packageUid, intent,
+                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+                    /* options= */ null, UserHandle.CURRENT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return pendingIntent;
+    }
+
     private final ResultReceiver mOnRequestConfirmationReceiver =
             new ResultReceiver(Handler.getMain()) {
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 85d3140..0426c79 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -549,6 +549,18 @@
         }
 
         @Override
+        public PendingIntent buildAssociationCancellationIntent(String packageName,
+                int userId) throws RemoteException {
+            Slog.i(TAG, "buildAssociationCancellationIntent() "
+                    + "package=u" + userId + "/" + packageName);
+            enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
+                    "build association cancellation intent");
+
+            return mAssociationRequestsProcessor.buildAssociationCancellationIntent(
+                    packageName, userId);
+        }
+
+        @Override
         public List<AssociationInfo> getAssociations(String packageName, int userId) {
             enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
                     "get associations");
@@ -1163,6 +1175,9 @@
     }
 
     private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+        if (packageInfo == null) {
+            return;
+        }
         if (containsEither(packageInfo.requestedPermissions,
                 android.Manifest.permission.RUN_IN_BACKGROUND,
                 android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index 9bad45b..3ab4aa8 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -54,12 +54,19 @@
     private static final String PROPERTY_PRIMARY_TAG =
             "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
 
-    static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
+    @Nullable
+    static PackageInfo getPackageInfo(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         final PackageManager pm = context.getPackageManager();
         final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
-        return Binder.withCleanCallingIdentity(() ->
-                pm.getPackageInfoAsUser(packageName, flags , userId));
+        return Binder.withCleanCallingIdentity(() -> {
+            try {
+                return pm.getPackageInfoAsUser(packageName, flags, userId);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "Package [" + packageName + "] is not found.");
+                return null;
+            }
+        });
     }
 
     static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 0aaa523..0b2cce0 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -184,13 +184,21 @@
     @MainThread
     private void startScan() {
         enforceInitialized();
-        // This method should not be called if scan is already in progress.
-        if (mScanning) throw new IllegalStateException("Scan is already in progress.");
-        // Neither should this method be called if the adapter is not available.
-        if (mBleScanner == null) throw new IllegalStateException("BLE is not available.");
 
         if (DEBUG) Log.i(TAG, "startScan()");
 
+        // This method should not be called if scan is already in progress.
+        if (mScanning) {
+            Slog.w(TAG, "Scan is already in progress.");
+            return;
+        }
+
+        // Neither should this method be called if the adapter is not available.
+        if (mBleScanner == null) {
+            Slog.w(TAG, "BLE is not available.");
+            return;
+        }
+
         // Collect MAC addresses from all associations.
         final Set<String> macAddresses = new HashSet<>();
         for (AssociationInfo association : mAssociationStore.getAssociations()) {
@@ -221,8 +229,18 @@
             filters.add(filter);
         }
 
-        mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
-        mScanning = true;
+        // BluetoothLeScanner will throw an IllegalStateException if startScan() is called while LE
+        // is not enabled.
+        if (mBtAdapter.isLeEnabled()) {
+            try {
+                mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
+                mScanning = true;
+            } catch (IllegalStateException e) {
+                Slog.w(TAG, "Exception while starting BLE scanning", e);
+            }
+        } else {
+            Slog.w(TAG, "BLE scanning is not turned on");
+        }
     }
 
     private void stopScanIfNeeded() {
@@ -240,11 +258,11 @@
         if (mBtAdapter.isLeEnabled()) {
             try {
                 mBleScanner.stopScan(mScanCallback);
-            } catch (RuntimeException e) {
-                // Just to be sure not to crash system server here if BluetoothLeScanner throws
-                // another RuntimeException.
+            } catch (IllegalStateException e) {
                 Slog.w(TAG, "Exception while stopping BLE scanning", e);
             }
+        } else {
+            Slog.w(TAG, "BLE scanning is not turned on");
         }
 
         mScanning = false;
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ec0da49..2904f28 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -22,14 +22,19 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraInjectionSession;
 import android.hardware.camera2.CameraManager;
+import android.os.Process;
+import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -45,6 +50,7 @@
     private final CameraAccessBlockedCallback mBlockedCallback;
     private final CameraManager mCameraManager;
     private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
 
     @GuardedBy("mLock")
     private int mObserverCount = 0;
@@ -66,7 +72,7 @@
 
     static class OpenCameraInfo {
         public String packageName;
-        public int packageUid;
+        public Set<Integer> packageUids;
     }
 
     interface CameraAccessBlockedCallback {
@@ -85,6 +91,7 @@
         mBlockedCallback = blockedCallback;
         mCameraManager = mContext.getSystemService(CameraManager.class);
         mPackageManager = mContext.getPackageManager();
+        mUserManager = mContext.getSystemService(UserManager.class);
     }
 
     /**
@@ -125,16 +132,18 @@
             for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) {
                 final String cameraId = mAppsToBlockOnVirtualDevice.keyAt(i);
                 final OpenCameraInfo openCameraInfo = mAppsToBlockOnVirtualDevice.get(cameraId);
-                int packageUid = openCameraInfo.packageUid;
-                if (runningUids.contains(packageUid)) {
-                    final String packageName = openCameraInfo.packageName;
-                    InjectionSessionData data = mPackageToSessionData.get(packageName);
-                    if (data == null) {
-                        data = new InjectionSessionData();
-                        data.appUid = packageUid;
-                        mPackageToSessionData.put(packageName, data);
+                final String packageName = openCameraInfo.packageName;
+                for (int packageUid : openCameraInfo.packageUids) {
+                    if (runningUids.contains(packageUid)) {
+                        InjectionSessionData data = mPackageToSessionData.get(packageName);
+                        if (data == null) {
+                            data = new InjectionSessionData();
+                            data.appUid = packageUid;
+                            mPackageToSessionData.put(packageName, data);
+                        }
+                        startBlocking(packageName, cameraId);
+                        break;
                     }
-                    startBlocking(packageName, cameraId);
                 }
             }
         }
@@ -155,37 +164,41 @@
     @Override
     public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
         synchronized (mLock) {
-            try {
-                final ApplicationInfo ainfo = mPackageManager.getApplicationInfo(packageName, 0);
-                InjectionSessionData data = mPackageToSessionData.get(packageName);
-                if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) {
-                    OpenCameraInfo openCameraInfo = new OpenCameraInfo();
-                    openCameraInfo.packageName = packageName;
-                    openCameraInfo.packageUid = ainfo.uid;
-                    mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo);
-                    CameraInjectionSession existingSession =
-                            (data != null) ? data.cameraIdToSession.get(cameraId) : null;
-                    if (existingSession != null) {
-                        existingSession.close();
-                        data.cameraIdToSession.remove(cameraId);
-                        if (data.cameraIdToSession.isEmpty()) {
-                            mPackageToSessionData.remove(packageName);
-                        }
+            InjectionSessionData data = mPackageToSessionData.get(packageName);
+            List<UserInfo> aliveUsers = mUserManager.getAliveUsers();
+            ArraySet<Integer> packageUids = new ArraySet<>();
+            for (UserInfo user : aliveUsers) {
+                int userId = user.getUserHandle().getIdentifier();
+                int appUid = queryUidFromPackageName(userId, packageName);
+                if (mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) {
+                    if (data == null) {
+                        data = new InjectionSessionData();
+                        data.appUid = appUid;
+                        mPackageToSessionData.put(packageName, data);
                     }
+                    if (data.cameraIdToSession.containsKey(cameraId)) {
+                        return;
+                    }
+                    startBlocking(packageName, cameraId);
                     return;
+                } else {
+                    if (appUid != Process.INVALID_UID) {
+                        packageUids.add(appUid);
+                    }
                 }
-                if (data == null) {
-                    data = new InjectionSessionData();
-                    data.appUid = ainfo.uid;
-                    mPackageToSessionData.put(packageName, data);
+            }
+            OpenCameraInfo openCameraInfo = new OpenCameraInfo();
+            openCameraInfo.packageName = packageName;
+            openCameraInfo.packageUids = packageUids;
+            mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo);
+            CameraInjectionSession existingSession =
+                    (data != null) ? data.cameraIdToSession.get(cameraId) : null;
+            if (existingSession != null) {
+                existingSession.close();
+                data.cameraIdToSession.remove(cameraId);
+                if (data.cameraIdToSession.isEmpty()) {
+                    mPackageToSessionData.remove(packageName);
                 }
-                if (data.cameraIdToSession.containsKey(cameraId)) {
-                    return;
-                }
-                startBlocking(packageName, cameraId);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e);
-                return;
             }
         }
     }
@@ -274,4 +287,16 @@
             }
         }
     }
+
+    private int queryUidFromPackageName(int userId, String packageName) {
+        try {
+            final ApplicationInfo ainfo =
+                    mPackageManager.getApplicationInfoAsUser(packageName,
+                        PackageManager.GET_ACTIVITIES, userId);
+            return ainfo.uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "queryUidFromPackageName - unknown package " + packageName, e);
+            return Process.INVALID_UID;
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 838cbd9..ec30369b 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -24,7 +24,6 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -42,6 +41,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
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 4204162..5f27f59 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -95,6 +95,7 @@
     private final AssociationInfo mAssociationInfo;
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
+    private final int mDeviceId;
     private final InputController mInputController;
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
@@ -140,19 +141,40 @@
     private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
             new SparseArray<>();
 
-    VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
-            IBinder token, int ownerUid, OnDeviceCloseListener listener,
+    VirtualDeviceImpl(
+            Context context,
+            AssociationInfo associationInfo,
+            IBinder token,
+            int ownerUid,
+            int deviceId,
+            OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
-        this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
-                pendingTrampolineCallback, activityListener, runningAppsChangedCallback, params);
+        this(
+                context,
+                associationInfo,
+                token,
+                ownerUid,
+                deviceId,
+                /* inputController= */ null,
+                listener,
+                pendingTrampolineCallback,
+                activityListener,
+                runningAppsChangedCallback,
+                params);
     }
 
     @VisibleForTesting
-    VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
-            int ownerUid, InputController inputController, OnDeviceCloseListener listener,
+    VirtualDeviceImpl(
+            Context context,
+            AssociationInfo associationInfo,
+            IBinder token,
+            int ownerUid,
+            int deviceId,
+            InputController inputController,
+            OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -164,6 +186,7 @@
         mActivityListener = activityListener;
         mRunningAppsChangedCallback = runningAppsChangedCallback;
         mOwnerUid = ownerUid;
+        mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
         if (inputController == null) {
@@ -199,6 +222,12 @@
         return mAssociationInfo.getDisplayName();
     }
 
+    /** Returns the unique device ID of this device. */
+    @Override // Binder call
+    public int getDeviceId() {
+        return mDeviceId;
+    }
+
     @Override // Binder call
     public int getAssociationId() {
         return mAssociationInfo.getId();
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 2b644fe..06dfeab 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -60,12 +60,12 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 @SuppressLint("LongLogTag")
 public class VirtualDeviceManagerService extends SystemService {
 
-    private static final boolean DEBUG = false;
     private static final String TAG = "VirtualDeviceManagerService";
 
     private final Object mVirtualDeviceManagerLock = new Object();
@@ -73,6 +73,10 @@
     private final VirtualDeviceManagerInternal mLocalService;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
+
+    private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
+            VirtualDeviceManager.DEFAULT_DEVICE_ID + 1);
+
     /**
      * Mapping from user IDs to CameraAccessControllers.
      */
@@ -260,8 +264,10 @@
                 final int userId = UserHandle.getUserId(callingUid);
                 final CameraAccessController cameraAccessController =
                         mCameraAccessControllers.get(userId);
+                final int uniqueId = sNextUniqueIndex.getAndIncrement();
+
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid,
+                        associationInfo, token, callingUid, uniqueId,
                         new VirtualDeviceImpl.OnDeviceCloseListener() {
                             @Override
                             public void onClose(int associationId) {
diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp
index 434f239..5392c2c 100644
--- a/services/contentcapture/Android.bp
+++ b/services/contentcapture/Android.bp
@@ -17,6 +17,9 @@
 java_library_static {
     name: "services.contentcapture",
     defaults: ["platform_service_defaults"],
-    srcs: [":services.contentcapture-sources"],
+    srcs: [
+        ":services.contentcapture-sources",
+        "java/**/*.logtags",
+    ],
     libs: ["services.core"],
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 7a95a8f..a08687f 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -60,6 +60,7 @@
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -69,6 +70,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
@@ -88,6 +90,10 @@
 
     private static final String TAG = ContentCapturePerUserService.class.getSimpleName();
 
+    private static final int EVENT_LOG_CONNECT_STATE_DIED = 0;
+    static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1;
+    static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2;
+
     @GuardedBy("mLock")
     private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();
 
@@ -190,9 +196,12 @@
         Slog.w(TAG, "remote service died: " + service);
         synchronized (mLock) {
             mZombie = true;
+            ComponentName serviceComponent = getServiceComponentName();
             writeServiceEvent(
                     FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED,
-                    getServiceComponentName());
+                    serviceComponent);
+            EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+                    EVENT_LOG_CONNECT_STATE_DIED, 0);
         }
     }
 
@@ -529,6 +538,15 @@
         return mConditionsByPkg.get(packageName);
     }
 
+    @Nullable
+    ArraySet<String> getContentCaptureAllowlist() {
+        ArraySet<String> allowPackages;
+        synchronized (mLock) {
+            allowPackages = mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId);
+        }
+        return allowPackages;
+    }
+
     @GuardedBy("mLock")
     void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
         if (mRemoteService == null) {
@@ -617,8 +635,12 @@
 
             ArraySet<String> oldList =
                     mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId);
+            EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId,
+                    CollectionUtils.size(oldList));
 
             mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
+            EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId,
+                    CollectionUtils.size(packages), CollectionUtils.size(activities));
             writeSetWhitelistEvent(getServiceComponentName(), packages, activities);
 
             updateContentCaptureOptions(oldList);
@@ -699,13 +721,15 @@
         private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) {
             ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions
                     .getWhitelistedPackages(mUserId);
+            int addingCount = CollectionUtils.size(adding);
+            EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, addingCount);
 
             if (oldList != null && adding != null) {
                 adding.removeAll(oldList);
             }
 
-            int N = adding != null ? adding.size() : 0;
-            for (int i = 0; i < N; i++) {
+            EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount);
+            for (int i = 0; i < addingCount; i++) {
                 String packageName = adding.valueAt(i);
                 ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions
                         .getOptions(mUserId, packageName);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
new file mode 100644
index 0000000..5218b26
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
@@ -0,0 +1,13 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.server.contentcapture
+
+# ContentCaptureService connection state change, refer to ContentCapturePerUserService
+# for type definition
+53200 cc_connect_state_changed (user|1|5),(type|1|5),(package_count|1|1)
+# Set the package and activity allowlist
+53201 cc_set_allowlist (user|1|5),(package_count|1|1),(activity_count|1|1)
+# Get the current allowlist
+53202 cc_current_allowlist (user|1|5),(count|1|1)
+# update content capture client option with new allow list count
+53203 cc_update_options (user|1|5),(count|1)
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 1efe55a..3907de4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -31,6 +31,7 @@
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.IDataShareCallback;
 import android.service.contentcapture.SnapshotData;
+import android.util.EventLog;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.DataRemovalRequest;
@@ -38,6 +39,7 @@
 
 import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
 import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 
 final class RemoteContentCaptureService
@@ -88,6 +90,10 @@
                     writeServiceEvent(
                             FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED,
                             mComponentName);
+                    EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED,
+                            mPerUserService.getUserId(),
+                            ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED,
+                            CollectionUtils.size(mPerUserService.getContentCaptureAllowlist()));
                 } finally {
                     // Update the system-service state, in case the service reconnected after
                     // dying
@@ -98,6 +104,9 @@
                 writeServiceEvent(
                         FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED,
                         mComponentName);
+                EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED,
+                        mPerUserService.getUserId(),
+                        ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED, 0);
             }
         } catch (Exception e) {
             Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index cbd3f60..3aed167 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -147,7 +147,8 @@
         "android.hardware.boot-V1.0-java",
         "android.hardware.boot-V1.1-java",
         "android.hardware.boot-V1.2-java",
-        "android.hardware.broadcastradio-V2.0-java",
+        "android.hardware.broadcastradio-V2.0-java", // HIDL
+        "android.hardware.broadcastradio-V1-java", // AIDL
         "android.hardware.health-V1.0-java", // HIDL
         "android.hardware.health-V2.0-java", // HIDL
         "android.hardware.health-V2.1-java", // HIDL
@@ -176,6 +177,13 @@
         "ImmutabilityAnnotation",
     ],
     javac_shard_size: 50,
+    javacflags: [
+        "-J--add-modules=jdk.compiler",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
 }
 
 java_genrule {
diff --git a/services/core/java/com/android/server/AccessibilityManagerInternal.java b/services/core/java/com/android/server/AccessibilityManagerInternal.java
index 6ca32af..faa45ca 100644
--- a/services/core/java/com/android/server/AccessibilityManagerInternal.java
+++ b/services/core/java/com/android/server/AccessibilityManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.view.inputmethod.EditorInfo;
@@ -49,6 +50,15 @@
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             EditorInfo editorInfo, boolean restarting);
 
+    /**
+     * Queries whether touch-exploration mode is enabled or not for the specified user.
+     *
+     * @param userId User ID to be queried about.
+     * @return {@code true} if touch-exploration mode is enabled.
+     * @see android.view.accessibility.AccessibilityManager#isTouchExplorationEnabled()
+     */
+    public abstract boolean isTouchExplorationEnabled(@UserIdInt int userId);
+
     private static final AccessibilityManagerInternal NOP = new AccessibilityManagerInternal() {
         @Override
         public void setImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions,
@@ -71,6 +81,11 @@
         public void startInput(IRemoteAccessibilityInputConnection remoteAccessibility,
                 EditorInfo editorInfo, boolean restarting) {
         }
+
+        @Override
+        public boolean isTouchExplorationEnabled(int userId) {
+            return false;
+        }
     };
 
     /**
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index e3c8afa..b6a8227 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -16,8 +16,12 @@
 
 package com.android.server;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.app.PendingIntent;
 
+import com.android.server.SystemClockTime.TimeConfidence;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
+
 public interface AlarmManagerInternal {
     // Some other components in the system server need to know about
     // broadcast alarms currently in flight
@@ -48,4 +52,25 @@
      * {@link android.Manifest.permission#USE_EXACT_ALARM}.
      */
     boolean hasExactAlarmPermission(String packageName, int uid);
+
+    /**
+     * Sets the device's current time zone and time zone confidence.
+     *
+     * @param tzId the time zone ID
+     * @param confidence the confidence that {@code tzId} is correct, see {@link TimeZoneConfidence}
+     *     for details
+     * @param logInfo the reason the time zone is being changed, for bug report logging
+     */
+    void setTimeZone(String tzId, @TimeZoneConfidence int confidence, String logInfo);
+
+    /**
+     * Sets the device's current time and time confidence.
+     *
+     * @param unixEpochTimeMillis the time
+     * @param confidence the confidence that {@code unixEpochTimeMillis} is correct, see {@link
+     *     TimeConfidence} for details
+     * @param logMsg the reason the time is being changed, for bug report logging
+     */
+    void setTime(@CurrentTimeMillisLong long unixEpochTimeMillis, @TimeConfidence int confidence,
+            String logMsg);
 }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b96d33c..4278b3e 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,9 +22,12 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.health.HealthInfo;
 import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -185,6 +188,17 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
+    private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
+    private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
+    private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
+    private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
+    private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
+
     private MetricsLogger mMetricsLogger;
 
     public BatteryService(Context context) {
@@ -606,7 +620,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mPowerConnectedOptions);
                     }
                 });
             }
@@ -617,7 +632,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mPowerDisconnectedOptions);
                     }
                 });
             }
@@ -630,7 +646,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mBatteryLowOptions);
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
@@ -642,7 +659,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mBatteryOkayOptions);
                     }
                 });
             }
@@ -712,7 +730,8 @@
                     + ", info:" + mHealthInfo.toString());
         }
 
-        mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));
+        mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, AppOpsManager.OP_NONE,
+                mBatteryChangedOptions, UserHandle.USER_ALL));
     }
 
     private void sendBatteryLevelChangedIntentLocked() {
diff --git a/services/core/java/com/android/server/DropBoxManagerInternal.java b/services/core/java/com/android/server/DropBoxManagerInternal.java
index 3785a9c..6ede8e9 100644
--- a/services/core/java/com/android/server/DropBoxManagerInternal.java
+++ b/services/core/java/com/android/server/DropBoxManagerInternal.java
@@ -34,7 +34,15 @@
      * to dynamically generate the entry contents.
      */
     public interface EntrySource extends Closeable {
-        public @BytesLong long length();
         public void writeTo(@NonNull FileDescriptor fd) throws IOException;
+
+        public default @BytesLong long length() {
+            // By default, length is unknown
+            return 0;
+        }
+
+        public default void close() throws IOException {
+            // By default, no resources to close
+        }
     }
 }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 6ff8e36..c441859 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -16,6 +16,7 @@
 # Health
 per-file BatteryService.java = file:platform/hardware/interfaces:/health/aidl/OWNERS
 
+per-file *Accessibility* = file:/services/accessibility/OWNERS
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
 per-file *Battery* = file:/BATTERY_STATS_OWNERS
@@ -35,6 +36,8 @@
 per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
 per-file PinnerService.java = file:/apct-tests/perftests/OWNERS
 per-file RescueParty.java = fdunlap@google.com, shuc@google.com
+per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
+per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file TelephonyRegistry.java = file:/telephony/OWNERS
 per-file UiModeManagerService.java = file:/packages/SystemUI/OWNERS
 per-file VcnManagementService.java = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5b1f740..83d527e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -147,7 +147,6 @@
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.server.pm.Installer;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.storage.AppFuseBridge;
@@ -557,7 +556,6 @@
     private IAppOpsService mIAppOpsService;
 
     private final Callbacks mCallbacks;
-    private final LockPatternUtils mLockPatternUtils;
 
     private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY =
             "anr_delay_millis";
@@ -1808,7 +1806,6 @@
                 ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
         mContext = context;
         mCallbacks = new Callbacks(FgThread.get().getLooper());
-        mLockPatternUtils = new LockPatternUtils(mContext);
 
         HandlerThread hthread = new HandlerThread(TAG);
         hthread.start();
@@ -3069,96 +3066,21 @@
         }
     }
 
-    private String encodeBytes(byte[] bytes) {
-        if (ArrayUtils.isEmpty(bytes)) {
-            return "!";
-        } else {
-            return HexDump.toHexString(bytes);
-        }
-    }
-
+    /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
-    /*
-     * Add this secret to the set of ways we can recover a user's disk
-     * encryption key.  Changing the secret for a disk encryption key is done in
-     * two phases.  First, this method is called to add the new secret binding.
-     * Second, fixateNewestUserKeyAuth is called to delete all other bindings.
-     * This allows other places where a credential is used, such as Gatekeeper,
-     * to be updated between the two calls.
-     */
     @Override
-    public void addUserKeyAuth(int userId, int serialNumber, byte[] secret) {
-
-        try {
-            mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(secret));
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
-        }
+    public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
+        mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
     }
 
+    /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
-    /*
-     * Store a user's disk encryption key without secret binding.  Removing the
-     * secret for a disk encryption key is done in two phases.  First, this
-     * method is called to retrieve the key using the provided secret and store
-     * it encrypted with a keystore key not bound to the user.  Second,
-     * fixateNewestUserKeyAuth is called to delete the key's other bindings.
-     */
     @Override
-    public void clearUserKeyAuth(int userId, int serialNumber, byte[] secret) {
-
-        try {
-            mVold.clearUserKeyAuth(userId, serialNumber, encodeBytes(secret));
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
+    public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
+        throws RemoteException {
+        if (StorageManager.isFileEncrypted()) {
+            mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
         }
-    }
-
-    @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
-    /*
-     * Delete all bindings of a user's disk encryption key except the most
-     * recently added one.
-     */
-    @Override
-    public void fixateNewestUserKeyAuth(int userId) {
-
-        try {
-            mVold.fixateNewestUserKeyAuth(userId);
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
-        }
-    }
-
-    @Override
-    public void unlockUserKey(int userId, int serialNumber, byte[] secret) {
-        boolean isFileEncrypted = StorageManager.isFileEncrypted();
-        Slog.d(TAG, "unlockUserKey: " + userId
-                + " isFileEncrypted: " + isFileEncrypted
-                + " hasSecret: " + (secret != null));
-        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-
-        if (isUserKeyUnlocked(userId)) {
-            Slog.d(TAG, "User " + userId + "'s CE storage is already unlocked");
-            return;
-        }
-
-        if (isFileEncrypted) {
-            // When a user has a secure lock screen, a secret is required to
-            // unlock the key, so don't bother trying to unlock it without one.
-            // This prevents misleading error messages from being logged.
-            if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) {
-                Slog.d(TAG, "Not unlocking user " + userId
-                        + "'s CE storage yet because a secret is needed");
-                return;
-            }
-            try {
-                mVold.unlockUserKey(userId, serialNumber, encodeBytes(secret));
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return;
-            }
-        }
-
         synchronized (mLock) {
             mLocalUnlockedUsers.append(userId);
         }
diff --git a/services/core/java/com/android/server/SystemClockTime.java b/services/core/java/com/android/server/SystemClockTime.java
new file mode 100644
index 0000000..46fbbb2
--- /dev/null
+++ b/services/core/java/com/android/server/SystemClockTime.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A set of static methods that encapsulate knowledge of how the system clock time and associated
+ * metadata are stored on Android.
+ */
+public final class SystemClockTime {
+
+    private static final String TAG = "SystemClockTime";
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's system clock
+     * time. This is logged in bug reports to assist with debugging issues with time.
+     */
+    @NonNull
+    private static final LocalLog sTimeDebugLog =
+            new LocalLog(30, false /* useLocalTimestamps */);
+
+
+    /**
+     * An annotation that indicates a "time confidence" value is expected.
+     *
+     * <p>The confidence indicates whether the time is expected to be correct. The confidence can be
+     * upgraded or downgraded over time. It can be used to decide whether a user could / should be
+     * asked to confirm the time. For example, during device set up low confidence would describe a
+     * time that has been initialized by default. The user may then be asked to confirm the time,
+     * moving it to a high confidence.
+     */
+    @Retention(SOURCE)
+    @Target(TYPE_USE)
+    @IntDef(prefix = "TIME_CONFIDENCE_",
+            value = { TIME_CONFIDENCE_LOW, TIME_CONFIDENCE_HIGH })
+    public @interface TimeConfidence {
+    }
+
+    /** Used when confidence is low and would (ideally) be confirmed by a user. */
+    public static final @TimeConfidence int TIME_CONFIDENCE_LOW = 0;
+
+    /**
+     * Used when confidence in the time is high and does not need to be confirmed by a user.
+     */
+    public static final @TimeConfidence int TIME_CONFIDENCE_HIGH = 100;
+
+    /**
+     * The confidence in the current time. Android's time confidence is held in memory because RTC
+     * hardware can forget / corrupt the time while the device is powered off. Therefore, on boot
+     * we can't assume the time is good, and so default it to "low" confidence until it is confirmed
+     * or explicitly set.
+     */
+    private static @TimeConfidence int sTimeConfidence = TIME_CONFIDENCE_LOW;
+
+    private static final long sNativeData = init();
+
+    private SystemClockTime() {
+    }
+
+    /**
+     * Sets the system clock time to a reasonable lower bound. Used during boot-up to ensure the
+     * device has a time that is better than a default like 1970-01-01.
+     */
+    public static void initializeIfRequired() {
+        // Use the most recent of Build.TIME, the root file system's timestamp, and the
+        // value of the ro.build.date.utc system property (which is in seconds).
+        final long systemBuildTime = Long.max(
+                1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
+                Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
+        long currentTimeMillis = getCurrentTimeMillis();
+        if (currentTimeMillis < systemBuildTime) {
+            String logMsg = "Current time only " + currentTimeMillis
+                    + ", advancing to build time " + systemBuildTime;
+            Slog.i(TAG, logMsg);
+            setTimeAndConfidence(systemBuildTime, TIME_CONFIDENCE_LOW, logMsg);
+        }
+    }
+
+    /**
+     * Sets the system clock time and confidence. See also {@link #setConfidence(int, String)} for
+     * an alternative that only sets the confidence.
+     *
+     * @param unixEpochMillis the time to set
+     * @param confidence the confidence in {@code unixEpochMillis}. See {@link TimeConfidence} for
+     *     details.
+     * @param logMsg a log message that can be included in bug reports that explains the update
+     */
+    public static void setTimeAndConfidence(
+            @CurrentTimeMillisLong long unixEpochMillis, int confidence, @NonNull String logMsg) {
+        synchronized (SystemClockTime.class) {
+            setTime(sNativeData, unixEpochMillis);
+            sTimeConfidence = confidence;
+            sTimeDebugLog.log(logMsg);
+        }
+    }
+
+    /**
+     * Sets the system clock confidence. See also {@link #setTimeAndConfidence(long, int, String)}
+     * for an alternative that sets the time and confidence.
+     *
+     * @param confidence the confidence in the system clock time. See {@link TimeConfidence} for
+     *     details.
+     * @param logMsg a log message that can be included in bug reports that explains the update
+     */
+    public static void setConfidence(@TimeConfidence int confidence, @NonNull String logMsg) {
+        synchronized (SystemClockTime.class) {
+            sTimeConfidence = confidence;
+            sTimeDebugLog.log(logMsg);
+        }
+    }
+
+    /**
+     * Returns the system clock time. The same as {@link System#currentTimeMillis()}.
+     */
+    private static @CurrentTimeMillisLong long getCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * Returns the system clock confidence. See {@link TimeConfidence} for details.
+     */
+    public static @TimeConfidence int getTimeConfidence() {
+        synchronized (SystemClockTime.class) {
+            return sTimeConfidence;
+        }
+    }
+
+    /**
+     * Adds an entry to the system time debug log that is included in bug reports. This method is
+     * intended to be used to record event that may lead to a time change, e.g. config or mode
+     * changes.
+     */
+    public static void addDebugLogEntry(@NonNull String logMsg) {
+        sTimeDebugLog.log(logMsg);
+    }
+
+    /**
+     * Dumps information about recent time / confidence changes to the supplied writer.
+     */
+    public static void dump(PrintWriter writer) {
+        sTimeDebugLog.dump(writer);
+    }
+
+    private static native long init();
+    private static native int setTime(long nativeData, @CurrentTimeMillisLong long millis);
+}
diff --git a/services/core/java/com/android/server/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java
new file mode 100644
index 0000000..dd07081
--- /dev/null
+++ b/services/core/java/com/android/server/SystemTimeZone.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A set of constants and static methods that encapsulate knowledge of how time zone and associated
+ * metadata are stored on Android.
+ */
+public final class SystemTimeZone {
+
+    private static final String TAG = "SystemTimeZone";
+    private static final boolean DEBUG = false;
+    private static final String TIME_ZONE_SYSTEM_PROPERTY = "persist.sys.timezone";
+    private static final String TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY =
+            "persist.sys.timezone_confidence";
+
+    /**
+     * The "special" time zone ID used as a low-confidence default when the device's time zone
+     * is empty or invalid during boot.
+     */
+    private static final String DEFAULT_TIME_ZONE_ID = "GMT";
+
+    /**
+     * An annotation that indicates a "time zone confidence" value is expected.
+     *
+     * <p>The confidence indicates whether the time zone is expected to be correct. The confidence
+     * can be upgraded or downgraded over time. It can be used to decide whether a user could /
+     * should be asked to confirm the time zone. For example, during device set up low confidence
+     * would describe a time zone that has been initialized by default or by using low quality
+     * or ambiguous signals. The user may then be asked to confirm the time zone, moving it to a
+     * high confidence.
+     */
+    @Retention(SOURCE)
+    @Target(TYPE_USE)
+    @IntDef(prefix = "TIME_ZONE_CONFIDENCE_",
+            value = { TIME_ZONE_CONFIDENCE_LOW, TIME_ZONE_CONFIDENCE_HIGH })
+    public @interface TimeZoneConfidence {
+    }
+
+    /** Used when confidence is low and would (ideally) be confirmed by a user. */
+    public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_LOW = 0;
+    /**
+     * Used when confidence in the time zone is high and does not need to be confirmed by a user.
+     */
+    public static final @TimeZoneConfidence int TIME_ZONE_CONFIDENCE_HIGH = 100;
+
+    /**
+     * An in-memory log that records the debug info related to the device's time zone setting.
+     * This is logged in bug reports to assist with debugging time zone detection issues.
+     */
+    @NonNull
+    private static final LocalLog sTimeZoneDebugLog =
+            new LocalLog(30, false /* useLocalTimestamps */);
+
+    private SystemTimeZone() {}
+
+    /**
+     * Called during device boot to validate and set the time zone ID to a low-confidence default.
+     */
+    public static void initializeTimeZoneSettingsIfRequired() {
+        String timezoneProperty = SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY);
+        if (!isValidTimeZoneId(timezoneProperty)) {
+            String logInfo = "initializeTimeZoneSettingsIfRequired():" + TIME_ZONE_SYSTEM_PROPERTY
+                    + " is not valid (" + timezoneProperty + "); setting to "
+                    + DEFAULT_TIME_ZONE_ID;
+            Slog.w(TAG, logInfo);
+            setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW, logInfo);
+        }
+    }
+
+    /**
+     * Adds an entry to the system time zone debug log that is included in bug reports. This method
+     * is intended to be used to record event that may lead to a time zone change, e.g. config or
+     * mode changes.
+     */
+    public static void addDebugLogEntry(@NonNull String logMsg) {
+        sTimeZoneDebugLog.log(logMsg);
+    }
+
+    /**
+     * Updates the device's time zone system property, associated metadata and adds an entry to the
+     * debug log. Returns {@code true} if the device's time zone changed, {@code false} if the ID is
+     * invalid or the device is already set to the supplied ID.
+     *
+     * <p>This method ensures the confidence metadata is set to the supplied value if the supplied
+     * time zone ID is considered valid.
+     *
+     * <p>This method is intended only for use by the AlarmManager. When changing the device's time
+     * zone other system service components must use {@link
+     * AlarmManagerInternal#setTimeZone(String, int, String)} to ensure that important
+     * system-wide side effects occur.
+     */
+    public static boolean setTimeZoneId(
+            @NonNull String timeZoneId, @TimeZoneConfidence int confidence,
+            @NonNull String logInfo) {
+        if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) {
+            addDebugLogEntry("setTimeZoneId: Invalid time zone ID."
+                    + " timeZoneId=" + timeZoneId
+                    + ", confidence=" + confidence
+                    + ", logInfo=" + logInfo);
+            return false;
+        }
+
+        boolean timeZoneChanged = false;
+        synchronized (SystemTimeZone.class) {
+            String currentTimeZoneId = getTimeZoneId();
+            if (currentTimeZoneId == null || !currentTimeZoneId.equals(timeZoneId)) {
+                SystemProperties.set(TIME_ZONE_SYSTEM_PROPERTY, timeZoneId);
+                if (DEBUG) {
+                    Slog.v(TAG, "Time zone changed: " + currentTimeZoneId + ", new=" + timeZoneId);
+                }
+                timeZoneChanged = true;
+            }
+            boolean timeZoneConfidenceChanged = setTimeZoneConfidence(confidence);
+            if (timeZoneChanged || timeZoneConfidenceChanged) {
+                String logMsg = "Time zone or confidence set: "
+                        + " (new) timeZoneId=" + timeZoneId
+                        + ", (new) confidence=" + confidence
+                        + ", logInfo=" + logInfo;
+                addDebugLogEntry(logMsg);
+            }
+        }
+
+        return timeZoneChanged;
+    }
+
+    /**
+     * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details.
+     */
+    private static boolean setTimeZoneConfidence(@TimeZoneConfidence int newConfidence) {
+        int currentConfidence = getTimeZoneConfidence();
+        if (currentConfidence != newConfidence) {
+            SystemProperties.set(
+                    TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(newConfidence));
+            if (DEBUG) {
+                Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence
+                        + ", newConfidence=" + newConfidence);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /** Returns the time zone confidence value. See {@link TimeZoneConfidence} for details. */
+    public static @TimeZoneConfidence int getTimeZoneConfidence() {
+        int confidence = SystemProperties.getInt(
+                TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, TIME_ZONE_CONFIDENCE_LOW);
+        if (!isValidTimeZoneConfidence(confidence)) {
+            confidence = TIME_ZONE_CONFIDENCE_LOW;
+        }
+        return confidence;
+    }
+
+    /** Returns the device's time zone ID setting. */
+    public static String getTimeZoneId() {
+        return SystemProperties.get(TIME_ZONE_SYSTEM_PROPERTY);
+    }
+
+    /**
+     * Dumps information about recent time zone decisions / changes to the supplied writer.
+     */
+    public static void dump(PrintWriter writer) {
+        sTimeZoneDebugLog.dump(writer);
+    }
+
+    private static boolean isValidTimeZoneConfidence(@TimeZoneConfidence int confidence) {
+        return confidence >= TIME_ZONE_CONFIDENCE_LOW && confidence <= TIME_ZONE_CONFIDENCE_HIGH;
+    }
+
+    private static boolean isValidTimeZoneId(String timeZoneId) {
+        return timeZoneId != null
+                && !timeZoneId.isEmpty()
+                && ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
+    }
+}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5c84a62..ae65dcb 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
     "presubmit": [
         {
+            "name": "CtsLocationFineTestCases"
+        },
+        {
             "name": "CtsLocationCoarseTestCases"
         },
         {
@@ -66,10 +69,5 @@
             ],
             "file_patterns": ["ClipboardService\\.java"]
         }
-    ],
-    "postsubmit": [
-        {
-            "name": "CtsLocationFineTestCases"
-        }
     ]
 }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index e81bab1..202f4775 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1831,7 +1831,7 @@
         if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
                 || mWindowManager.isKeyguardShowingAndNotOccluded()
                 || !mPowerManager.isInteractive())) {
-            Sandman.startDreamWhenDockedIfAppropriate(getContext());
+            mInjector.startDreamWhenDockedIfAppropriate(getContext());
         }
     }
 
@@ -2148,5 +2148,9 @@
         public int getCallingUid() {
             return Binder.getCallingUid();
         }
+
+        public void startDreamWhenDockedIfAppropriate(Context context) {
+            Sandman.startDreamWhenDockedIfAppropriate(context);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f26d9f9..76cac93 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -24,6 +24,7 @@
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -167,6 +168,10 @@
     static final String VCN_CONFIG_FILE =
             new File(Environment.getDataSystemDirectory(), "vcn/configs.xml").getPath();
 
+    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
+
     /* Binder context for this service */
     @NonNull private final Context mContext;
     @NonNull private final Dependencies mDeps;
@@ -360,15 +365,12 @@
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
     public void systemReady() {
-        // Always run on the handler thread to ensure consistency.
-        mHandler.post(() -> {
-            mNetworkProvider.register();
-            mContext.getSystemService(ConnectivityManager.class)
-                    .registerNetworkCallback(
-                            new NetworkRequest.Builder().clearCapabilities().build(),
-                            mTrackingNetworkCallback);
-            mTelephonySubscriptionTracker.register();
-        });
+        mNetworkProvider.register();
+        mContext.getSystemService(ConnectivityManager.class)
+                .registerNetworkCallback(
+                        new NetworkRequest.Builder().clearCapabilities().build(),
+                        mTrackingNetworkCallback);
+        mTelephonySubscriptionTracker.register();
     }
 
     private void enforcePrimaryUser() {
@@ -509,15 +511,22 @@
                         if (!mVcns.containsKey(subGrp)) {
                             startVcnLocked(subGrp, entry.getValue());
                         }
+
+                        // Cancel any scheduled teardowns for active subscriptions
+                        mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
                     }
                 }
 
-                // Schedule teardown of any VCN instances that have lost carrier privileges
+                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
+                // delay)
                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
                     final ParcelUuid subGrp = entry.getKey();
                     final VcnConfig config = mConfigs.get(subGrp);
 
                     final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
+                    final boolean isValidActiveDataSubIdNotInVcnSubGrp =
+                            isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
+                                    && !isActiveSubGroup(subGrp, snapshot);
 
                     // TODO(b/193687515): Support multiple VCNs active at the same time
                     if (config == null
@@ -527,12 +536,31 @@
                         final ParcelUuid uuidToTeardown = subGrp;
                         final Vcn instanceToTeardown = entry.getValue();
 
-                        stopVcnLocked(uuidToTeardown);
+                        // TODO(b/193687515): Support multiple VCNs active at the same time
+                        // If directly switching to a subscription not in the current group,
+                        // teardown immediately to prevent other subscription's network from being
+                        // outscored by the VCN. Otherwise, teardown after a delay to ensure that
+                        // SIM profile switches do not trigger the VCN to cycle.
+                        final long teardownDelayMs =
+                                isValidActiveDataSubIdNotInVcnSubGrp
+                                        ? 0
+                                        : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
+                        mHandler.postDelayed(() -> {
+                            synchronized (mLock) {
+                                // Guard against case where this is run after a old instance was
+                                // torn down, and a new instance was started. Verify to ensure
+                                // correct instance is torn down. This could happen as a result of a
+                                // Carrier App manually removing/adding a VcnConfig.
+                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
+                                    stopVcnLocked(uuidToTeardown);
 
-                        // TODO(b/181789060): invoke asynchronously after Vcn notifies
-                        // through VcnCallback
-                        notifyAllPermissionedStatusCallbacksLocked(
-                                uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+                                    // TODO(b/181789060): invoke asynchronously after Vcn notifies
+                                    // through VcnCallback
+                                    notifyAllPermissionedStatusCallbacksLocked(
+                                            uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+                                }
+                            }
+                        }, instanceToTeardown, teardownDelayMs);
                     } else {
                         // If this VCN's status has not changed, update it with the new snapshot
                         entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index eb0f3f0..672ee0e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -89,6 +89,7 @@
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -3100,7 +3101,7 @@
                              */
                             if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent)) {
+                                    result)) {
                                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                                         "invalid intent in bundle returned");
                                 return;
@@ -3519,7 +3520,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
@@ -4870,7 +4871,13 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-         protected boolean checkKeyIntent(int authUid, Intent intent) {
+        protected boolean checkKeyIntent(int authUid, Bundle bundle) {
+            if (!checkKeyIntentParceledCorrectly(bundle)) {
+                EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
+                return false;
+            }
+
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
             // Explicitly set an empty ClipData to ensure that we don't offer to
             // promote any Uris contained inside for granting purposes
             if (intent.getClipData() == null) {
@@ -4905,6 +4912,25 @@
             }
         }
 
+        /**
+         * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't
+         * violate our security policy.
+         *
+         * In particular we want to make sure the Authenticator doesn't trick users
+         * into launching arbitrary intents on the device via exploiting any other Parcel read/write
+         * mismatch problems.
+         */
+        private boolean checkKeyIntentParceledCorrectly(Bundle bundle) {
+            Parcel p = Parcel.obtain();
+            p.writeBundle(bundle);
+            p.setDataPosition(0);
+            Bundle simulateBundle = p.readBundle();
+            p.recycle();
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+            return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+                Intent.class)));
+        }
+
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
             String className = activityInfo.name;
             return "android".equals(activityInfo.packageName) &&
@@ -5051,7 +5077,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5e7d814..16fe121 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -735,6 +735,9 @@
     // initialized in the constructor.
     public int CUR_MAX_EMPTY_PROCESSES;
 
+    /** @see mEnforceReceiverExportedFlagRequirement */
+    private static final String KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT =
+            "enforce_exported_flag_requirement";
 
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED =
@@ -744,6 +747,9 @@
     private static final String KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS =
             "no_kill_cached_processes_post_boot_completed_duration_millis";
 
+    /** @see mEnforceReceiverExportedFlagRequirement */
+    private static final boolean DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT = false;
+
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final boolean DEFAULT_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED = true;
 
@@ -752,6 +758,15 @@
             DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS = 600_000;
 
     /**
+     * If true, enforce the requirement that dynamically registered receivers specify one of
+     * {@link android.content.Context#RECEIVER_EXPORTED} or
+     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} if registering for any non-system
+     * broadcasts.
+     */
+    volatile boolean mEnforceReceiverExportedFlagRequirement =
+            DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT;
+
+    /**
      * If true, do not kill excessive cached processes proactively, until user-0 is unlocked.
      * @see #mNoKillCachedProcessesPostBootCompletedDurationMillis
      */
@@ -1010,6 +1025,9 @@
                             case KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED:
                                 updateNoKillCachedProcessesUntilBootCompleted();
                                 break;
+                            case KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT:
+                                updateEnforceReceiverExportedFlagRequirement();
+                                break;
                             case KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS:
                                 updateNoKillCachedProcessesPostBootCompletedDurationMillis();
                                 break;
@@ -1482,6 +1500,13 @@
                 DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST);
     }
 
+    private void updateEnforceReceiverExportedFlagRequirement() {
+        mEnforceReceiverExportedFlagRequirement = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT,
+                DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
+    }
+
     private void updateNoKillCachedProcessesUntilBootCompleted() {
         mNoKillCachedProcessesUntilBootCompleted = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1819,6 +1844,8 @@
         pw.print("="); pw.println(mPrioritizeAlarmBroadcasts);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED);
         pw.print("="); pw.println(mNoKillCachedProcessesUntilBootCompleted);
+        pw.print("  "); pw.print(KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
+        pw.print("="); pw.println(mEnforceReceiverExportedFlagRequirement);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS);
         pw.print("="); pw.println(mNoKillCachedProcessesPostBootCompletedDurationMillis);
         pw.print("  "); pw.print(KEY_MAX_EMPTY_TIME_MILLIS);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7be3dca..b34fe69 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.Manifest.permission.MANAGE_USERS;
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
 import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
@@ -214,8 +215,8 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -245,6 +246,8 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionInfo;
+import android.content.pm.PermissionMethod;
+import android.content.pm.PermissionName;
 import android.content.pm.ProcessInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ProviderInfoList;
@@ -257,6 +260,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.media.audiofx.AudioEffect;
 import android.net.ConnectivityManager;
@@ -331,6 +335,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.ProtoUtils;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -583,7 +588,7 @@
      * unprotected broadcast in code.
      */
     @ChangeId
-    @Disabled
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
@@ -2365,6 +2370,7 @@
         mUiHandler = injector.getUiHandler(null /* service */);
         mUidObserverController = new UidObserverController(mUiHandler);
         mUserController = new UserController(this);
+        mInjector.mUserController = mUserController;
         mPendingIntentController =
                 new PendingIntentController(handlerThread.getLooper(), mUserController, mConstants);
         mAppRestrictionController = new AppRestrictionController(mContext, this);
@@ -2435,12 +2441,12 @@
 
         mEnableOffloadQueue = SystemProperties.getBoolean(
                 "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
-        mEnableModernQueue = SystemProperties.getBoolean(
-                "persist.device_config.activity_manager_native_boot.modern_queue_enabled", false);
+        mEnableModernQueue = foreConstants.MODERN_QUEUE_ENABLED;
 
         if (mEnableModernQueue) {
             mBroadcastQueues = new BroadcastQueue[1];
-            mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, foreConstants);
+            mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler,
+                    foreConstants, backConstants);
         } else {
             mBroadcastQueues = new BroadcastQueue[4];
             mBroadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(this, mHandler,
@@ -2478,6 +2484,7 @@
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
 
         mUserController = new UserController(this);
+        mInjector.mUserController = mUserController;
 
         mPendingIntentController = new PendingIntentController(
                 mHandlerThread.getLooper(), mUserController, mConstants);
@@ -2927,10 +2934,13 @@
                 || event == Event.ACTIVITY_DESTROYED)) {
             contentCaptureService.notifyActivityEvent(userId, activity, event);
         }
-        // TODO(b/201234353): Move the logic to client side.
-        if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
-                || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
-            mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+        // Currently we have move most of logic to the client side. When the activity lifecycle
+        // event changed, the client side will notify the VoiceInteractionManagerService. But
+        // when the application process died, the VoiceInteractionManagerService will miss the
+        // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to
+        // know if the activity has been destroyed.
+        if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) {
+            mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken);
         }
     }
 
@@ -3520,7 +3530,7 @@
             if (subject != null || criticalEventSection != null) {
                 appendtoANRFile(tracesFile.getAbsolutePath(),
                         (subject != null ? "Subject: " + subject + "\n\n" : "")
-                        + criticalEventSection != null ? criticalEventSection : "");
+                        + (criticalEventSection != null ? criticalEventSection : ""));
             }
 
             Pair<Long, Long> offsets = dumpStackTraces(
@@ -4157,6 +4167,12 @@
             //  Yeah, um, no.
             return;
         }
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, callingUserId)) {
+            Slog.w(TAG, "Failed trying to add dependency on non-existing package: " + packageName);
+            return;
+        }
         ProcessRecord proc;
         synchronized (mPidsSelfLocked) {
             proc = mPidsSelfLocked.get(Binder.getCallingPid());
@@ -4514,7 +4530,7 @@
         // Clean-up disabled broadcast receivers.
         for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
             mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    packageName, disabledClasses, userId, true);
+                    packageName, disabledClasses, userId);
         }
 
     }
@@ -4523,7 +4539,7 @@
         boolean didSomething = false;
         for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
             didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                    null, null, userId, true);
+                    null, null, userId);
         }
         return didSomething;
     }
@@ -4659,7 +4675,7 @@
         if (doit) {
             for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
                 didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                        packageName, null, userId, doit);
+                        packageName, null, userId);
             }
         }
 
@@ -5322,7 +5338,14 @@
     }
 
     private void showMteOverrideNotificationIfActive() {
-        if (!SystemProperties.getBoolean("ro.arm64.memtag.bootctl_supported", false)
+        String bootctl = SystemProperties.get("arm64.memtag.bootctl");
+        // If MTE is on, there is one in three cases:
+        // * a fullmte build: ro.arm64.memtag.bootctl_supported is not set
+        // * memtag: arm64.memtag.bootctl contains "memtag"
+        // * memtag-once
+        // In the condition below we detect memtag-once by exclusion.
+        if (Arrays.asList(bootctl.split(",")).contains("memtag")
+            || !SystemProperties.getBoolean("ro.arm64.memtag.bootctl_supported", false)
             || !com.android.internal.os.Zygote.nativeSupportsMemoryTagging()) {
             return;
         }
@@ -5962,7 +5985,14 @@
         }
     }
 
-    public static int checkComponentPermission(String permission, int pid, int uid,
+    /**
+     * 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)}.
+     */
+    @PackageManager.PermissionResult
+    @PermissionMethod
+    public static int checkComponentPermission(@PermissionName String permission, int pid, int uid,
             int owningUid, boolean exported) {
         if (pid == MY_PID) {
             return PackageManager.PERMISSION_GRANTED;
@@ -6008,7 +6038,9 @@
      * This can be called with or without the global lock held.
      */
     @Override
-    public int checkPermission(String permission, int pid, int uid) {
+    @PackageManager.PermissionResult
+    @PermissionMethod
+    public int checkPermission(@PermissionName String permission, int pid, int uid) {
         if (permission == null) {
             return PackageManager.PERMISSION_DENIED;
         }
@@ -6019,7 +6051,9 @@
      * Binder IPC calls go through the public entry point.
      * This can be called with or without the global lock held.
      */
-    int checkCallingPermission(String permission) {
+    @PackageManager.PermissionResult
+    @PermissionMethod
+    int checkCallingPermission(@PermissionName String permission) {
         return checkPermission(permission,
                 Binder.getCallingPid(),
                 Binder.getCallingUid());
@@ -6028,7 +6062,8 @@
     /**
      * This can be called with or without the global lock held.
      */
-    void enforceCallingPermission(String permission, String func) {
+    @PermissionMethod
+    void enforceCallingPermission(@PermissionName String permission, String func) {
         if (checkCallingPermission(permission)
                 == PackageManager.PERMISSION_GRANTED) {
             return;
@@ -6045,7 +6080,26 @@
     /**
      * This can be called with or without the global lock held.
      */
-    void enforcePermission(String permission, int pid, int uid, String func) {
+    private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
+        for (String permission : permissions) {
+            if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+        }
+
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires one of " + Arrays.toString(permissions);
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
+    /**
+     * This can be called with or without the global lock held.
+     */
+    @PermissionMethod
+    void enforcePermission(@PermissionName String permission, int pid, int uid, String func) {
         if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
             return;
         }
@@ -6557,6 +6611,10 @@
         }
     }
 
+    void appNotResponding(@NonNull ProcessRecord anrProcess, @NonNull TimeoutRecord timeoutRecord) {
+        mAnrHelper.appNotResponding(anrProcess, timeoutRecord);
+    }
+
     void startPersistentApps(int matchFlags) {
         if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
 
@@ -8927,6 +8985,7 @@
      * @param incrementalMetrics metrics for apps installed on Incremental.
      * @param errorId a unique id to append to the dropbox headers.
      */
+    @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to worker.run()
     public void addErrorToDropBox(String eventType,
             ProcessRecord process, String processName, String activityShortComponentName,
             String parentShortComponentName, ProcessRecord parentProcess,
@@ -10637,8 +10696,11 @@
         }
     }
 
+    @NeverCompile
     void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
+        boolean dumpConstants = true;
+        boolean dumpHistory = true;
         boolean needSep = false;
         boolean onlyHistory = false;
         boolean printedAnything = false;
@@ -10723,7 +10785,8 @@
 
         if (!onlyReceivers) {
             for (BroadcastQueue q : mBroadcastQueues) {
-                needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep);
+                needSep = q.dumpLocked(fd, pw, args, opti,
+                        dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
                 printedAnything |= needSep;
             }
         }
@@ -10781,6 +10844,7 @@
         }
     }
 
+    @NeverCompile
     void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         if (mCurBroadcastStats == null) {
@@ -10814,6 +10878,7 @@
         }
     }
 
+    @NeverCompile
     void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean fullCheckin, String dumpPackage) {
         if (mCurBroadcastStats == null) {
@@ -13383,7 +13448,8 @@
             // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
             // not be used generally, so we will be marking them as exported by default
             final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
+                    && mConstants.mEnforceReceiverExportedFlagRequirement;
             if (!onlyProtectedBroadcasts) {
                 if (receiver == null && !explicitExportStateDefined) {
                     // sticky broadcast, no flag specified (flag isn't required)
@@ -16316,27 +16382,47 @@
 
     @Override
     public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+        int[] displayIds = getSecondaryDisplayIdsForStartingBackgroundUsers();
+        boolean validDisplay = false;
+        if (displayIds != null) {
+            for (int i = 0; i < displayIds.length; i++) {
+                if (displayId == displayIds[i]) {
+                    validDisplay = true;
+                    break;
+                }
+            }
+        }
+        if (!validDisplay) {
+            throw new IllegalArgumentException("Invalid display (" + displayId + ") to start user. "
+                    + "Valid options are: " + Arrays.toString(displayIds));
+        }
+
+        if (DEBUG_MU) {
+            Slogf.d(TAG_MU, "Calling startUserOnSecondaryDisplay(%d, %d) using injector %s", userId,
+                    displayId, mInjector);
+        }
         // Permission check done inside UserController.
-        return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+        return mInjector.startUserOnSecondaryDisplay(userId, displayId);
     }
 
-    /**
-     * Unlocks the given user.
-     *
-     * @param userId The ID of the user to unlock.
-     * @param token No longer used.  (This parameter cannot be removed because
-     *              this method is marked with UnsupportedAppUsage, so its
-     *              signature might not be safe to change.)
-     * @param secret The secret needed to unlock the user's credential-encrypted
-     *               storage, or null if no secret is needed.
-     * @param listener An optional progress listener.
-     *
-     * @return true if the user was successfully unlocked, otherwise false.
-     */
     @Override
-    public boolean unlockUser(int userId, @Nullable byte[] token, @Nullable byte[] secret,
-            @Nullable IProgressListener listener) {
-        return mUserController.unlockUser(userId, secret, listener);
+    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        enforceCallingHasAtLeastOnePermission("getSecondaryDisplayIdsForStartingBackgroundUsers()",
+                MANAGE_USERS, INTERACT_ACROSS_USERS);
+        return mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+    }
+
+    /** @deprecated see the AIDL documentation {@inheritDoc} */
+    @Override
+    @Deprecated
+    public boolean unlockUser(@UserIdInt int userId, @Nullable byte[] token,
+            @Nullable byte[] secret, @Nullable IProgressListener listener) {
+        return mUserController.unlockUser(userId, listener);
+    }
+
+    @Override
+    public boolean unlockUser2(@UserIdInt int userId, @Nullable IProgressListener listener) {
+        return mUserController.unlockUser(userId, listener);
     }
 
     @Override
@@ -17349,6 +17435,8 @@
                     bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
                             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                             PowerExemptionManager.REASON_LOCALE_CHANGED, "");
+                    bOptions.setRemoveMatchingFilter(
+                            new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
                             null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
                             SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
@@ -18324,8 +18412,10 @@
 
     @VisibleForTesting
     public static class Injector {
+        private final Context mContext;
         private NetworkManagementInternal mNmi;
-        private Context mContext;
+
+        private UserController mUserController;
 
         public Injector(Context context) {
             mContext = context;
@@ -18351,6 +18441,103 @@
         }
 
         /**
+         * Called by {@code AMS.getSecondaryDisplayIdsForStartingBackgroundUsers()}.
+         */
+        // NOTE: ideally Injector should have no complex logic, but if this logic was moved to AMS,
+        // it could not be tested with the existing ActivityManagerServiceTest (as DisplayManager,
+        // DisplayInfo, etc... are final and UserManager.isUsersOnSecondaryDisplaysEnabled is
+        // static).
+        // So, the logic was added here, and tested on ActivityManagerServiceInjectorTest (which
+        // was added on FrameworksMockingServicesTests and hence uses Extended Mockito to mock
+        // final and static stuff)
+        @Nullable
+        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+                Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): not supported");
+                return null;
+            }
+
+            // NOTE: DisplayManagerInternal doesn't have a method to list all displays
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+            Display[] allDisplays = displayManager.getDisplays();
+
+            // allDisplays should contain at least Display.DEFAULT_DISPLAY, but it's better to
+            // double check, just in case...
+            if (allDisplays == null || allDisplays.length == 0) {
+                Slogf.wtf(TAG, "displayManager (%s) returned no displays", displayManager);
+                return null;
+            }
+            boolean hasDefaultDisplay = false;
+            for (Display display : allDisplays) {
+                if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    hasDefaultDisplay = true;
+                    break;
+                }
+            }
+            if (!hasDefaultDisplay) {
+                Slogf.wtf(TAG, "displayManager (%s) has %d displays (%s), but none has id "
+                        + "DEFAULT_DISPLAY (%d)", displayManager, allDisplays.length,
+                        Arrays.toString(allDisplays), Display.DEFAULT_DISPLAY);
+                return null;
+            }
+
+            // Starts with all displays but DEFAULT_DISPLAY
+            int[] displayIds = new int[allDisplays.length - 1];
+
+            // TODO(b/247592632): check for other properties like isSecure or proper display type
+            int numberValidDisplays = 0;
+            for (Display display : allDisplays) {
+                int displayId = display.getDisplayId();
+                if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+                    displayIds[numberValidDisplays++] = displayId;
+                }
+            }
+
+            if (numberValidDisplays == 0) {
+                // TODO(b/247580038): remove this workaround once a virtual display on Car's
+                // KitchenSink (or other app) can be used while running CTS tests on devices that
+                // don't have a real display.
+                // STOPSHIP: if not removed, it should at least be unit tested
+                String testingProp = "fw.secondary_display_for_starting_users_for_testing_purposes";
+                int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
+                if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
+                    Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+                            + "display found, but returning %d as set by property %s", displayId,
+                            testingProp);
+                    return new int[] { displayId };
+                }
+                Slogf.e(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid display"
+                        + " on %s", Arrays.toString(allDisplays));
+                return null;
+            }
+
+            if (numberValidDisplays != displayIds.length) {
+                int[] validDisplayIds = new int[numberValidDisplays];
+                System.arraycopy(displayIds, 0, validDisplayIds, 0, numberValidDisplays);
+                if (DEBUG_MU) {
+                    Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning "
+                            + "only valid displays (%d instead of %d): %s", numberValidDisplays,
+                            displayIds.length, Arrays.toString(validDisplayIds));
+                }
+                return validDisplayIds;
+            }
+
+            if (DEBUG_MU) {
+                Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning all "
+                        + "(but DEFAULT_DISPLAY) displays : %s", Arrays.toString(displayIds));
+            }
+            return displayIds;
+        }
+
+        /**
+         * Called by {@code AMS.startUserOnSecondaryDisplay()}.
+         */
+        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+            return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+        }
+
+        /**
          * Return the process list instance
          */
         public ProcessList getProcessList(ActivityManagerService service) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index b4f6e35..e4f947d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -367,6 +367,8 @@
                     return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
                 case "reset-dropbox-rate-limiter":
                     return runResetDropboxRateLimiter();
+                case "list-secondary-displays-for-starting-users":
+                    return runListSecondaryDisplaysForStartingUsers(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -2068,6 +2070,10 @@
             success = mInterface.startUserInBackgroundWithListener(userId, waiter);
             displaySuffix = "";
         } else {
+            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+                pw.println("Not supported");
+                return -1;
+            }
             success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
             displaySuffix = " on display " + displayId;
         }
@@ -2113,7 +2119,7 @@
             return -1;
         }
 
-        boolean success = mInterface.unlockUser(userId, null, null, null);
+        boolean success = mInterface.unlockUser2(userId, null);
         if (success) {
             pw.println("Success: user unlocked");
         } else {
@@ -3591,6 +3597,14 @@
         return 0;
     }
 
+    int runListSecondaryDisplaysForStartingUsers(PrintWriter pw) throws RemoteException {
+        int[] displayIds = mInterface.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        pw.println(displayIds == null || displayIds.length == 0
+                ? "none"
+                : Arrays.toString(displayIds));
+        return 0;
+    }
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -3951,6 +3965,9 @@
             pw.println("         Set an app's background restriction level which in turn map to a app standby bucket.");
             pw.println("  get-bg-restriction-level [--user <USER_ID>] <PACKAGE>");
             pw.println("         Get an app's background restriction level.");
+            pw.println("  list-secondary-displays-for-starting-users");
+            pw.println("         Lists the id of displays that can be used to start users on "
+                    + "background.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3299ee3..b7de57f8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -44,6 +44,7 @@
 import static com.android.server.am.ActivityManagerService.getKsmInfo;
 import static com.android.server.am.ActivityManagerService.stringifyKBSize;
 import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD;
 
@@ -1047,17 +1048,7 @@
                 }
                 trimMemoryUiHiddenIfNecessaryLSP(app);
                 if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
-                    if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ,
-                                        "Trimming memory of " + app.processName
-                                        + " to " + curLevel[0]);
-                            }
-                            thread.scheduleTrimMemory(curLevel[0]);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of ");
                     profile.setTrimMemoryLevel(curLevel[0]);
                     step[0]++;
                     if (step[0] >= actualFactor) {
@@ -1073,31 +1064,11 @@
                     }
                 } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
                         && !app.isKilledByAm()) {
-                    if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
-                            && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ,
-                                        "Trimming memory of heavy-weight " + app.processName
-                                        + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
-                            }
-                            thread.scheduleTrimMemory(
-                                    ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
+                            "Trimming memory of heavy-weight ");
                     profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                 } else {
-                    if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName
-                                        + " to " + fgTrimLevel);
-                            }
-                            thread.scheduleTrimMemory(fgTrimLevel);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg ");
                     profile.setTrimMemoryLevel(fgTrimLevel);
                 }
             });
@@ -1128,22 +1099,28 @@
             // If this application is now in the background and it
             // had done UI, then give it the special trim level to
             // have it free UI resources.
-            final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
-            IApplicationThread thread;
-            if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
-                try {
-                    if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                        Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
-                                + app.processName + " to " + level);
-                    }
-                    thread.scheduleTrimMemory(level);
-                } catch (RemoteException e) {
-                }
-            }
+            scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
+                    "Trimming memory of bg-ui ");
             app.mProfile.setPendingUiClean(false);
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
+        IApplicationThread thread;
+        if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
+            try {
+                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
+                    Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
+                }
+                mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
+                        OOM_ADJ_REASON_NONE);
+                thread.scheduleTrimMemory(level);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     @GuardedBy("mProcLock")
     long getLowRamTimeSinceIdleLPr(long now) {
         return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
@@ -1915,6 +1892,12 @@
         }
     }
 
+    long getCpuDelayTimeForPid(int pid) {
+        synchronized (mProcessCpuTracker) {
+            return mProcessCpuTracker.getCpuDelayTimeForPid(pid);
+        }
+    }
+
     List<ProcessCpuTracker.Stats> getCpuStats(Predicate<ProcessCpuTracker.Stats> predicate) {
         synchronized (mProcessCpuTracker) {
             return mProcessCpuTracker.getStats(st -> predicate.test(st));
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a41a311..d9d29d65 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -831,6 +831,26 @@
     }
 
     @Override
+    @EnforcePermission(BATTERY_STATS)
+    public long computeBatteryScreenOffRealtimeMs() {
+        synchronized (mStats) {
+            final long curTimeUs = SystemClock.elapsedRealtimeNanos() / 1000;
+            long timeUs = mStats.computeBatteryScreenOffRealtime(curTimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            return timeUs / 1000;
+        }
+    }
+
+    @Override
+    @EnforcePermission(BATTERY_STATS)
+    public long getScreenOffDischargeMah() {
+        synchronized (mStats) {
+            long dischargeUah = mStats.getUahDischargeScreenOff(BatteryStats.STATS_SINCE_CHARGED);
+            return dischargeUah / 1000;
+        }
+    }
+
+    @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteEvent(final int code, final String name, final int uid) {
         if (name == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 2ebe0b4..a4a1c2f 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -16,7 +16,11 @@
 
 package com.android.server.am;
 
+import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT;
+
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
@@ -24,12 +28,17 @@
 import android.database.ContentObserver;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import java.io.PrintWriter;
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -39,6 +48,9 @@
 public class BroadcastConstants {
     private static final String TAG = "BroadcastConstants";
 
+    // TODO: migrate remaining constants to be loaded from DeviceConfig
+    // TODO: migrate fg/bg values into single constants instance
+
     // Value element names within the Settings record
     static final String KEY_TIMEOUT = "bcast_timeout";
     static final String KEY_SLOW_TIME = "bcast_slow_time";
@@ -115,6 +127,74 @@
     // started its process can start a background activity.
     public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
 
+    /**
+     * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
+     * of the default {@link BroadcastQueueImpl}.
+     */
+    public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
+    private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
+    private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = false;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
+     * dispatch broadcasts to simultaneously.
+     */
+    public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
+    private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues";
+    private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
+     * to dispatch to a "running" process queue before we retire them back to
+     * being "runnable" to give other processes a chance to run.
+     */
+    public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
+    private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
+    private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of pending
+     * broadcasts to hold for a process before we ignore any delays that policy
+     * might have applied to that process.
+     */
+    public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
+    private static final String KEY_MAX_PENDING_BROADCASTS = "bcast_max_pending_broadcasts";
+    private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Delay to apply to normal
+     * broadcasts, giving a chance for debouncing of rapidly changing events.
+     */
+    public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
+    private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
+    private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
+     * targeting cached applications.
+     */
+    public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
+    private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
+    private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of complete
+     * historical broadcasts to retain for debugging purposes.
+     */
+    public int MAX_HISTORY_COMPLETE_SIZE = DEFAULT_MAX_HISTORY_COMPLETE_SIZE;
+    private static final String KEY_MAX_HISTORY_COMPLETE_SIZE = "bcast_max_history_complete_size";
+    private static final int DEFAULT_MAX_HISTORY_COMPLETE_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of summarized
+     * historical broadcasts to retain for debugging purposes.
+     */
+    public int MAX_HISTORY_SUMMARY_SIZE = DEFAULT_MAX_HISTORY_SUMMARY_SIZE;
+    private static final String KEY_MAX_HISTORY_SUMMARY_SIZE = "bcast_max_history_summary_size";
+    private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -128,7 +208,7 @@
 
         @Override
         public void onChange(boolean selfChange) {
-            updateConstants();
+            updateSettingsConstants();
         }
     }
 
@@ -136,6 +216,9 @@
     // that instance's values are drawn.
     public BroadcastConstants(String settingsKey) {
         mSettingsKey = settingsKey;
+
+        // Load initial values at least once before we start observing below
+        updateDeviceConfigConstants();
     }
 
     /**
@@ -148,12 +231,15 @@
         mSettingsObserver = new SettingsObserver(handler);
         mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey),
                 false, mSettingsObserver);
+        updateSettingsConstants();
 
-        updateConstants();
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                new HandlerExecutor(handler), this::updateDeviceConfigConstants);
+        updateDeviceConfigConstants();
     }
 
-    private void updateConstants() {
-        synchronized (mParser) {
+    private void updateSettingsConstants() {
+        synchronized (this) {
             try {
                 mParser.setString(Settings.Global.getString(mResolver, mSettingsKey));
             } catch (IllegalArgumentException e) {
@@ -174,37 +260,103 @@
     }
 
     /**
+     * Return the {@link SystemProperty} name for the given key in our
+     * {@link DeviceConfig} namespace.
+     */
+    private @NonNull String propertyFor(@NonNull String key) {
+        return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
+    }
+
+    /**
+     * Return the {@link SystemProperty} name for the given key in our
+     * {@link DeviceConfig} namespace, but with a different prefix that can be
+     * used to locally override the {@link DeviceConfig} value.
+     */
+    private @NonNull String propertyOverrideFor(@NonNull String key) {
+        return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
+    }
+
+    private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
+        return SystemProperties.getBoolean(propertyOverrideFor(key),
+                SystemProperties.getBoolean(propertyFor(key), def));
+    }
+
+    private int getDeviceConfigInt(@NonNull String key, int def) {
+        return SystemProperties.getInt(propertyOverrideFor(key),
+                SystemProperties.getInt(propertyFor(key), def));
+    }
+
+    private long getDeviceConfigLong(@NonNull String key, long def) {
+        return SystemProperties.getLong(propertyOverrideFor(key),
+                SystemProperties.getLong(propertyFor(key), def));
+    }
+
+    private void updateDeviceConfigConstants(@NonNull DeviceConfig.Properties properties) {
+        updateDeviceConfigConstants();
+    }
+
+    /**
+     * Since our values are stored in a "native boot" namespace, we load them
+     * directly from the system properties.
+     */
+    private void updateDeviceConfigConstants() {
+        synchronized (this) {
+            MODERN_QUEUE_ENABLED = getDeviceConfigBoolean(KEY_MODERN_QUEUE_ENABLED,
+                    DEFAULT_MODERN_QUEUE_ENABLED);
+            MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
+                    DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+            MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
+                    DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+            MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
+                    DEFAULT_MAX_PENDING_BROADCASTS);
+            DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS,
+                    DEFAULT_DELAY_NORMAL_MILLIS);
+            DELAY_CACHED_MILLIS = getDeviceConfigLong(KEY_DELAY_CACHED_MILLIS,
+                    DEFAULT_DELAY_CACHED_MILLIS);
+            MAX_HISTORY_COMPLETE_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_COMPLETE_SIZE,
+                    DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
+            MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
+                    DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+        }
+    }
+
+    /**
      * Standard dumpsys support; invoked from BroadcastQueue dump
      */
-    public void dump(PrintWriter pw) {
-        synchronized (mParser) {
-            pw.println();
-            pw.print("  Broadcast parameters (key=");
+    @NeverCompile
+    public void dump(@NonNull IndentingPrintWriter pw) {
+        synchronized (this) {
+            pw.print("Broadcast parameters (key=");
             pw.print(mSettingsKey);
             pw.print(", observing=");
             pw.print(mSettingsObserver != null);
             pw.println("):");
-
-            pw.print("    "); pw.print(KEY_TIMEOUT); pw.print(" = ");
-            TimeUtils.formatDuration(TIMEOUT, pw);
+            pw.increaseIndent();
+            pw.print(KEY_TIMEOUT, TimeUtils.formatDuration(TIMEOUT)).println();
+            pw.print(KEY_SLOW_TIME, TimeUtils.formatDuration(SLOW_TIME)).println();
+            pw.print(KEY_DEFERRAL, TimeUtils.formatDuration(DEFERRAL)).println();
+            pw.print(KEY_DEFERRAL_DECAY_FACTOR, DEFERRAL_DECAY_FACTOR).println();
+            pw.print(KEY_DEFERRAL_FLOOR, DEFERRAL_FLOOR).println();
+            pw.print(KEY_ALLOW_BG_ACTIVITY_START_TIMEOUT,
+                    TimeUtils.formatDuration(ALLOW_BG_ACTIVITY_START_TIMEOUT)).println();
+            pw.decreaseIndent();
             pw.println();
 
-            pw.print("    "); pw.print(KEY_SLOW_TIME); pw.print(" = ");
-            TimeUtils.formatDuration(SLOW_TIME, pw);
-            pw.println();
-
-            pw.print("    "); pw.print(KEY_DEFERRAL); pw.print(" = ");
-            TimeUtils.formatDuration(DEFERRAL, pw);
-            pw.println();
-
-            pw.print("    "); pw.print(KEY_DEFERRAL_DECAY_FACTOR); pw.print(" = ");
-            pw.println(DEFERRAL_DECAY_FACTOR);
-
-            pw.print("    "); pw.print(KEY_DEFERRAL_FLOOR); pw.print(" = ");
-            TimeUtils.formatDuration(DEFERRAL_FLOOR, pw);
-
-            pw.print("    "); pw.print(KEY_ALLOW_BG_ACTIVITY_START_TIMEOUT); pw.print(" = ");
-            TimeUtils.formatDuration(ALLOW_BG_ACTIVITY_START_TIMEOUT, pw);
+            pw.print("Broadcast parameters (namespace=");
+            pw.print(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT);
+            pw.println("):");
+            pw.increaseIndent();
+            pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println();
+            pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println();
+            pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println();
+            pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println();
+            pw.print(KEY_DELAY_NORMAL_MILLIS,
+                    TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println();
+            pw.print(KEY_DELAY_CACHED_MILLIS,
+                    TimeUtils.formatDuration(DELAY_CACHED_MILLIS)).println();
+            pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
+            pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+            pw.decreaseIndent();
             pw.println();
         }
     }
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
index 4c44343..2adcf2f 100644
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -20,7 +20,9 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
@@ -36,6 +38,8 @@
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -80,12 +84,14 @@
             return broadcasts.isEmpty();
         }
 
+        @NeverCompile
         void dumpDebug(ProtoOutputStream proto, long fieldId) {
             for (BroadcastRecord br : broadcasts) {
                 br.dumpDebug(proto, fieldId);
             }
         }
 
+        @NeverCompile
         void dumpLocked(Dumper d) {
             for (BroadcastRecord br : broadcasts) {
                 d.dump(br);
@@ -143,6 +149,7 @@
             return mPrinted;
         }
 
+        @NeverCompile
         void dump(BroadcastRecord br) {
             if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
                 if (!mPrinted) {
@@ -422,6 +429,7 @@
             return size;
         }
 
+        @NeverCompile
         public void dump(Dumper dumper, String action) {
             SparseArray<BroadcastRecord> brs = getDeferredList(action);
             if (brs == null) {
@@ -432,6 +440,7 @@
             }
         }
 
+        @NeverCompile
         public void dumpDebug(ProtoOutputStream proto, long fieldId) {
             for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
                 mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
@@ -441,6 +450,7 @@
             }
         }
 
+        @NeverCompile
         private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
             for (int i = 0, size = brs.size(); i < size; i++) {
                 final Object receiver = brs.valueAt(i).receivers.get(0);
@@ -540,6 +550,38 @@
         }
     }
 
+    private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
+            @UptimeMillisLong long barrierTime) {
+        for (int i = 0; i < list.size(); i++) {
+            if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
+            @UptimeMillisLong long barrierTime) {
+        for (int i = 0; i < list.size(); i++) {
+            if (list.get(i).enqueueTime <= barrierTime) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
+        synchronized (mLock) {
+            if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
+                return false;
+            }
+            return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
+                    && isBeyondBarrier(mAlarmQueue, barrierTime)
+                    && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
+                    && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
+        }
+    }
+
     private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
         int pending = 0;
         final int numEntries = list.size();
@@ -806,6 +848,7 @@
     /**
      * Standard proto dump entry point
      */
+    @NeverCompile
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         if (mCurrentBroadcast != null) {
             mCurrentBroadcast.dumpDebug(proto, fieldId);
@@ -1133,6 +1176,7 @@
 
     // ----------------------------------
 
+    @NeverCompile
     boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
             SimpleDateFormat sdf) {
         final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 8e38f0a..a92723e 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -21,6 +21,8 @@
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 
 final class BroadcastFilter extends IntentFilter {
@@ -53,6 +55,7 @@
         exported = _exported;
     }
 
+    @NeverCompile
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
         super.dumpDebug(proto, BroadcastFilterProto.INTENT_FILTER);
@@ -64,20 +67,24 @@
         proto.end(token);
     }
 
+    @NeverCompile
     public void dump(PrintWriter pw, String prefix) {
         dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
         receiverList.dumpLocal(pw, prefix);
     }
 
+    @NeverCompile
     public void dumpBrief(PrintWriter pw, String prefix) {
         dumpBroadcastFilterState(pw, prefix);
     }
 
+    @NeverCompile
     public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
         super.dump(pr, prefix);
         dumpBroadcastFilterState(pw, prefix);
     }
 
+    @NeverCompile
     void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
         if (requiredPermission != null) {
             pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java
index d820d6c..6ac0e8b 100644
--- a/services/core/java/com/android/server/am/BroadcastHistory.java
+++ b/services/core/java/com/android/server/am/BroadcastHistory.java
@@ -16,12 +16,14 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
+import android.annotation.NonNull;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -31,22 +33,32 @@
  * for debugging purposes. Automatically trims itself over time.
  */
 public class BroadcastHistory {
-    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
-    static final int MAX_BROADCAST_SUMMARY_HISTORY
-            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+    private final int MAX_BROADCAST_HISTORY;
+    private final int MAX_BROADCAST_SUMMARY_HISTORY;
+
+    public BroadcastHistory(@NonNull BroadcastConstants constants) {
+        MAX_BROADCAST_HISTORY = constants.MAX_HISTORY_COMPLETE_SIZE;
+        MAX_BROADCAST_SUMMARY_HISTORY = constants.MAX_HISTORY_SUMMARY_SIZE;
+
+        mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+        mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+        mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+        mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+        mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+    }
 
     /**
      * Historical data of past broadcasts, for debugging.  This is a ring buffer
      * whose last element is at mHistoryNext.
      */
-    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+    final BroadcastRecord[] mBroadcastHistory;
     int mHistoryNext = 0;
 
     /**
      * Summary of historical data of past broadcasts, for debugging.  This is a
      * ring buffer whose last element is at mSummaryHistoryNext.
      */
-    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+    final Intent[] mBroadcastSummaryHistory;
     int mSummaryHistoryNext = 0;
 
     /**
@@ -54,9 +66,9 @@
      * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
      * clock time, not elapsed.
      */
-    final long[] mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+    final long[] mSummaryHistoryEnqueueTime;
+    final long[] mSummaryHistoryDispatchTime;
+    final long[] mSummaryHistoryFinishTime;
 
     public void addBroadcastToHistoryLocked(BroadcastRecord original) {
         // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
@@ -80,6 +92,7 @@
         else return x;
     }
 
+    @NeverCompile
     public void dumpDebug(ProtoOutputStream proto) {
         int lastIndex = mHistoryNext;
         int ringIndex = lastIndex;
@@ -113,6 +126,7 @@
         } while (ringIndex != lastIndex);
     }
 
+    @NeverCompile
     public boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
             SimpleDateFormat sdf, boolean dumpAll, boolean needSep) {
         int i;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c18c65e..97635b5 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -16,17 +16,33 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.BroadcastQueue.checkState;
+import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
+import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
+import android.content.pm.ResolveInfo;
+import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
 import android.util.IndentingPrintWriter;
+import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Objects;
 
 /**
  * Queue of pending {@link BroadcastRecord} entries intended for delivery to a
@@ -39,21 +55,13 @@
  * Internally each queue consists of a pending broadcasts which are waiting to
  * be dispatched, and a single active broadcast which is currently being
  * dispatched.
+ * <p>
+ * This entire class is marked as {@code NotThreadSafe} since it's the
+ * responsibility of the caller to always interact with a relevant lock held.
  */
-class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
-    /**
-     * Default delay to apply to background broadcasts, giving a chance for
-     * debouncing of rapidly changing events.
-     */
-    // TODO: shift hard-coded defaults to BroadcastConstants
-    private static final long DELAY_DEFAULT_MILLIS = 10_000;
-
-    /**
-     * Default delay to apply to broadcasts targeting cached applications.
-     */
-    // TODO: shift hard-coded defaults to BroadcastConstants
-    private static final long DELAY_CACHED_MILLIS = 30_000;
-
+// @NotThreadSafe
+class BroadcastProcessQueue {
+    final @NonNull BroadcastConstants constants;
     final @NonNull String processName;
     final int uid;
 
@@ -61,7 +69,14 @@
      * Linked list connection to another process under this {@link #uid} which
      * has a different {@link #processName}.
      */
-    @Nullable BroadcastProcessQueue next;
+    @Nullable BroadcastProcessQueue processNameNext;
+
+    /**
+     * Linked list connections to runnable process with lower and higher
+     * {@link #getRunnableAt()} times.
+     */
+    @Nullable BroadcastProcessQueue runnableAtNext;
+    @Nullable BroadcastProcessQueue runnableAtPrev;
 
     /**
      * Currently known details about the target process; typically undefined
@@ -70,6 +85,17 @@
     @Nullable ProcessRecord app;
 
     /**
+     * Track name to use for {@link Trace} events.
+     */
+    @Nullable String traceTrackName;
+
+    /**
+     * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
+     * used when deciding if we should extend the soft ANR timeout.
+     */
+    long lastCpuDelayTime;
+
+    /**
      * Ordered collection of broadcasts that are waiting to be dispatched to
      * this process, as a pair of {@link BroadcastRecord} and the index into
      * {@link BroadcastRecord#receivers} that represents the receiver.
@@ -87,17 +113,39 @@
      */
     private int mActiveIndex;
 
+    /**
+     * Count of {@link #mActive} broadcasts that have been dispatched since this
+     * queue was last idle.
+     */
+    private int mActiveCountSinceIdle;
+
+    /**
+     * Flag indicating that the currently active broadcast is being dispatched
+     * was scheduled via a cold start.
+     */
+    private boolean mActiveViaColdStart;
+
+    /**
+     * Count of {@link #mPending} broadcasts of these various flavors.
+     */
     private int mCountForeground;
     private int mCountOrdered;
     private int mCountAlarm;
+    private int mCountPrioritized;
 
     private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
+    private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
 
     private boolean mProcessCached;
 
-    public BroadcastProcessQueue(@NonNull String processName, int uid) {
-        this.processName = processName;
+    private String mCachedToString;
+    private String mCachedToShortString;
+
+    public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
+            @NonNull String processName, int uid) {
+        this.constants = Objects.requireNonNull(constants);
+        this.processName = Objects.requireNonNull(processName);
         this.uid = uid;
     }
 
@@ -105,29 +153,97 @@
      * Enqueue the given broadcast to be dispatched to this process at some
      * future point in time. The target receiver is indicated by the given index
      * into {@link BroadcastRecord#receivers}.
+     * <p>
+     * If the broadcast is marked as {@link BroadcastRecord#isReplacePending()},
+     * then this call will replace any pending dispatch; otherwise it will
+     * enqueue as a normal broadcast.
+     * <p>
+     * When defined, this receiver is considered "blocked" until at least the
+     * given count of other receivers have reached a terminal state; typically
+     * used for ordered broadcasts and priority traunches.
      */
-    public void enqueueBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
-        // Detect situations where the incoming broadcast should cause us to
-        // recalculate when we'll be runnable
-        if (mPending.isEmpty()) {
-            invalidateRunnableAt();
+    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
+            int blockedUntilTerminalCount) {
+        // If caller wants to replace, walk backwards looking for any matches
+        if (record.isReplacePending()) {
+            final Iterator<SomeArgs> it = mPending.descendingIterator();
+            final Object receiver = record.receivers.get(recordIndex);
+            while (it.hasNext()) {
+                final SomeArgs args = it.next();
+                final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
+                final Object testReceiver = testRecord.receivers.get(args.argi1);
+                if ((record.callingUid == testRecord.callingUid)
+                        && (record.userId == testRecord.userId)
+                        && record.intent.filterEquals(testRecord.intent)
+                        && isReceiverEquals(receiver, testReceiver)) {
+                    // Exact match found; perform in-place swap
+                    args.arg1 = record;
+                    args.argi1 = recordIndex;
+                    args.argi2 = blockedUntilTerminalCount;
+                    onBroadcastDequeued(testRecord);
+                    onBroadcastEnqueued(record);
+                    return;
+                }
+            }
         }
-        if (record.isForeground()) {
-            mCountForeground++;
-            invalidateRunnableAt();
-        }
-        if (record.ordered) {
-            mCountOrdered++;
-            invalidateRunnableAt();
-        }
-        if (record.alarm) {
-            mCountAlarm++;
-            invalidateRunnableAt();
-        }
+
+        // Caller isn't interested in replacing, or we didn't find any pending
+        // item to replace above, so enqueue as a new broadcast
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = record;
         args.argi1 = recordIndex;
+        args.argi2 = blockedUntilTerminalCount;
         mPending.addLast(args);
+        onBroadcastEnqueued(record);
+    }
+
+    /**
+     * Functional interface that tests a {@link BroadcastRecord} that has been
+     * previously enqueued in {@link BroadcastProcessQueue}.
+     */
+    @FunctionalInterface
+    public interface BroadcastPredicate {
+        public boolean test(@NonNull BroadcastRecord r, int index);
+    }
+
+    /**
+     * Functional interface that consumes a {@link BroadcastRecord} that has
+     * been previously enqueued in {@link BroadcastProcessQueue}.
+     */
+    @FunctionalInterface
+    public interface BroadcastConsumer {
+        public void accept(@NonNull BroadcastRecord r, int index);
+    }
+
+    /**
+     * Invoke given consumer for any broadcasts matching given predicate. If
+     * requested, matching broadcasts will also be removed from this queue.
+     * <p>
+     * Predicates that choose to remove a broadcast <em>must</em> finish
+     * delivery of the matched broadcast, to ensure that situations like ordered
+     * broadcasts are handled consistently.
+     */
+    public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
+            @NonNull BroadcastConsumer consumer, boolean andRemove) {
+        boolean didSomething = false;
+        final Iterator<SomeArgs> it = mPending.iterator();
+        while (it.hasNext()) {
+            final SomeArgs args = it.next();
+            final BroadcastRecord record = (BroadcastRecord) args.arg1;
+            final int index = args.argi1;
+            if (predicate.test(record, index)) {
+                consumer.accept(record, index);
+                if (andRemove) {
+                    args.recycle();
+                    it.remove();
+                    onBroadcastDequeued(record);
+                }
+                didSomething = true;
+            }
+        }
+        // TODO: also check any active broadcast once we have a better "nonce"
+        // representing each scheduled broadcast to avoid races
+        return didSomething;
     }
 
     /**
@@ -157,31 +273,41 @@
                 && (mActive.isForeground() || mActive.ordered || mActive.alarm)) {
             // We have an important broadcast right now, so boost priority
             return ProcessList.SCHED_GROUP_DEFAULT;
-        } else {
+        } else if (!isIdle()) {
             return ProcessList.SCHED_GROUP_BACKGROUND;
+        } else {
+            return ProcessList.SCHED_GROUP_UNDEFINED;
         }
     }
 
     /**
+     * Count of {@link #mActive} broadcasts that have been dispatched since this
+     * queue was last idle.
+     */
+    public int getActiveCountSinceIdle() {
+        return mActiveCountSinceIdle;
+    }
+
+    public void setActiveViaColdStart(boolean activeViaColdStart) {
+        mActiveViaColdStart = activeViaColdStart;
+    }
+
+    public boolean getActiveViaColdStart() {
+        return mActiveViaColdStart;
+    }
+
+    /**
      * Set the currently active broadcast to the next pending broadcast.
      */
     public void makeActiveNextPending() {
         // TODO: what if the next broadcast isn't runnable yet?
-        checkState(isRunnable(), "isRunnable");
         final SomeArgs next = mPending.removeFirst();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
+        mActiveCountSinceIdle++;
+        mActiveViaColdStart = false;
         next.recycle();
-        if (mActive.isForeground()) {
-            mCountForeground--;
-        }
-        if (mActive.ordered) {
-            mCountOrdered--;
-        }
-        if (mActive.alarm) {
-            mCountAlarm--;
-        }
-        invalidateRunnableAt();
+        onBroadcastDequeued(mActive);
     }
 
     /**
@@ -190,27 +316,124 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
+        mActiveCountSinceIdle = 0;
+        mActiveViaColdStart = false;
+        invalidateRunnableAt();
     }
 
-    public void setActiveDeliveryState(int deliveryState) {
-        checkState(isActive(), "isActive");
-        mActive.setDeliveryState(mActiveIndex, deliveryState);
+    /**
+     * Update summary statistics when the given record has been enqueued.
+     */
+    private void onBroadcastEnqueued(@NonNull BroadcastRecord record) {
+        if (record.isForeground()) {
+            mCountForeground++;
+        }
+        if (record.ordered) {
+            mCountOrdered++;
+        }
+        if (record.alarm) {
+            mCountAlarm++;
+        }
+        if (record.prioritized) {
+            mCountPrioritized++;
+        }
+        invalidateRunnableAt();
     }
 
+    /**
+     * Update summary statistics when the given record has been dequeued.
+     */
+    private void onBroadcastDequeued(@NonNull BroadcastRecord record) {
+        if (record.isForeground()) {
+            mCountForeground--;
+        }
+        if (record.ordered) {
+            mCountOrdered--;
+        }
+        if (record.alarm) {
+            mCountAlarm--;
+        }
+        if (record.prioritized) {
+            mCountPrioritized--;
+        }
+        invalidateRunnableAt();
+    }
+
+    public void traceProcessStartingBegin() {
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, toShortString() + " starting", hashCode());
+    }
+
+    public void traceProcessRunningBegin() {
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, toShortString() + " running", hashCode());
+    }
+
+    public void traceProcessEnd() {
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, hashCode());
+    }
+
+    public void traceActiveBegin() {
+        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, mActive.toShortString() + " scheduled", cookie);
+    }
+
+    public void traceActiveEnd() {
+        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, cookie);
+    }
+
+    /**
+     * Return the broadcast being actively dispatched in this process.
+     */
     public @NonNull BroadcastRecord getActive() {
-        checkState(isActive(), "isActive");
-        return mActive;
+        return Objects.requireNonNull(mActive);
     }
 
-    public @NonNull Object getActiveReceiver() {
-        checkState(isActive(), "isActive");
-        return mActive.receivers.get(mActiveIndex);
+    /**
+     * Return the index into {@link BroadcastRecord#receivers} of the receiver
+     * being actively dispatched in this process.
+     */
+    public int getActiveIndex() {
+        Objects.requireNonNull(mActive);
+        return mActiveIndex;
+    }
+
+    public boolean isEmpty() {
+        return mPending.isEmpty();
     }
 
     public boolean isActive() {
         return mActive != null;
     }
 
+    /**
+     * Quickly determine if this queue has broadcasts that are still waiting to
+     * be delivered at some point in the future.
+     */
+    public boolean isIdle() {
+        return !isActive() && isEmpty();
+    }
+
+    /**
+     * Quickly determine if this queue has broadcasts enqueued before the given
+     * barrier timestamp that are still waiting to be delivered.
+     */
+    public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
+        if (mActive != null) {
+            return mActive.enqueueTime > barrierTime;
+        }
+        final SomeArgs next = mPending.peekFirst();
+        if (next != null) {
+            return ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
+        }
+        // Nothing running or runnable means we're past the barrier
+        return true;
+    }
+
     public boolean isRunnable() {
         if (mRunnableAtInvalidated) updateRunnableAt();
         return mRunnableAt != Long.MAX_VALUE;
@@ -230,69 +453,266 @@
         return mRunnableAt;
     }
 
-    private void invalidateRunnableAt() {
+    /**
+     * Return the "reason" behind the current {@link #getRunnableAt()} value,
+     * such as indicating why the queue is being delayed or paused.
+     */
+    public @Reason int getRunnableAtReason() {
+        if (mRunnableAtInvalidated) updateRunnableAt();
+        return mRunnableAtReason;
+    }
+
+    public void invalidateRunnableAt() {
         mRunnableAtInvalidated = true;
     }
 
+    static final int REASON_EMPTY = 0;
+    static final int REASON_CONTAINS_FOREGROUND = 1;
+    static final int REASON_CONTAINS_ORDERED = 2;
+    static final int REASON_CONTAINS_ALARM = 3;
+    static final int REASON_CONTAINS_PRIORITIZED = 4;
+    static final int REASON_CACHED = 5;
+    static final int REASON_NORMAL = 6;
+    static final int REASON_MAX_PENDING = 7;
+    static final int REASON_BLOCKED = 8;
+
+    @IntDef(flag = false, prefix = { "REASON_" }, value = {
+            REASON_EMPTY,
+            REASON_CONTAINS_FOREGROUND,
+            REASON_CONTAINS_ORDERED,
+            REASON_CONTAINS_ALARM,
+            REASON_CONTAINS_PRIORITIZED,
+            REASON_CACHED,
+            REASON_NORMAL,
+            REASON_MAX_PENDING,
+            REASON_BLOCKED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Reason {}
+
+    static @NonNull String reasonToString(@Reason int reason) {
+        switch (reason) {
+            case REASON_EMPTY: return "EMPTY";
+            case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
+            case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
+            case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
+            case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED";
+            case REASON_CACHED: return "CACHED";
+            case REASON_NORMAL: return "NORMAL";
+            case REASON_MAX_PENDING: return "MAX_PENDING";
+            case REASON_BLOCKED: return "BLOCKED";
+            default: return Integer.toString(reason);
+        }
+    }
+
     /**
      * Update {@link #getRunnableAt()} if it's currently invalidated.
      */
     private void updateRunnableAt() {
         final SomeArgs next = mPending.peekFirst();
         if (next != null) {
-            final long runnableAt = ((BroadcastRecord) next.arg1).enqueueTime;
+            final BroadcastRecord r = (BroadcastRecord) next.arg1;
+            final int index = next.argi1;
+            final int blockedUntilTerminalCount = next.argi2;
+            final long runnableAt = r.enqueueTime;
+
+            // We might be blocked waiting for other receivers to finish,
+            // typically for an ordered broadcast or priority traunches
+            if (r.terminalCount < blockedUntilTerminalCount
+                    && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+                mRunnableAt = Long.MAX_VALUE;
+                mRunnableAtReason = REASON_BLOCKED;
+                return;
+            }
+
+            // If we have too many broadcasts pending, bypass any delays that
+            // might have been applied above to aid draining
+            if (mPending.size() >= constants.MAX_PENDING_BROADCASTS) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_MAX_PENDING;
+                return;
+            }
+
             if (mCountForeground > 0) {
                 mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
             } else if (mCountOrdered > 0) {
                 mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_ORDERED;
             } else if (mCountAlarm > 0) {
                 mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_ALARM;
+            } else if (mCountPrioritized > 0) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
             } else if (mProcessCached) {
-                mRunnableAt = runnableAt + DELAY_CACHED_MILLIS;
+                mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
+                mRunnableAtReason = REASON_CACHED;
             } else {
-                mRunnableAt = runnableAt + DELAY_DEFAULT_MILLIS;
+                mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
+                mRunnableAtReason = REASON_NORMAL;
             }
         } else {
             mRunnableAt = Long.MAX_VALUE;
+            mRunnableAtReason = REASON_EMPTY;
         }
     }
 
-    @Override
-    public int compareTo(BroadcastProcessQueue o) {
-        if (mRunnableAtInvalidated) updateRunnableAt();
-        if (o.mRunnableAtInvalidated) o.updateRunnableAt();
-        return Long.compare(mRunnableAt, o.mRunnableAt);
+    /**
+     * Check overall health, confirming things are in a reasonable state and
+     * that we're not wedged.
+     */
+    public void checkHealthLocked() {
+        if (mRunnableAtReason == REASON_BLOCKED) {
+            final SomeArgs next = mPending.peekFirst();
+            Objects.requireNonNull(next, "peekFirst");
+
+            // If blocked more than 10 minutes, we're likely wedged
+            final BroadcastRecord r = (BroadcastRecord) next.arg1;
+            final long waitingTime = SystemClock.uptimeMillis() - r.enqueueTime;
+            checkState(waitingTime < (10 * DateUtils.MINUTE_IN_MILLIS), "waitingTime");
+        }
+    }
+
+    /**
+     * Insert the given queue into a sorted linked list of "runnable" queues.
+     *
+     * @param head the current linked list head
+     * @param item the queue to insert
+     * @return a potentially updated linked list head
+     */
+    @VisibleForTesting
+    static @Nullable BroadcastProcessQueue insertIntoRunnableList(
+            @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
+        if (head == null) {
+            return item;
+        }
+        final long itemRunnableAt = item.getRunnableAt();
+        BroadcastProcessQueue test = head;
+        BroadcastProcessQueue tail = null;
+        while (test != null) {
+            if (test.getRunnableAt() >= itemRunnableAt) {
+                item.runnableAtNext = test;
+                item.runnableAtPrev = test.runnableAtPrev;
+                if (item.runnableAtNext != null) {
+                    item.runnableAtNext.runnableAtPrev = item;
+                }
+                if (item.runnableAtPrev != null) {
+                    item.runnableAtPrev.runnableAtNext = item;
+                }
+                return (test == head) ? item : head;
+            }
+            tail = test;
+            test = test.runnableAtNext;
+        }
+        item.runnableAtPrev = tail;
+        item.runnableAtPrev.runnableAtNext = item;
+        return head;
+    }
+
+    /**
+     * Remove the given queue from a sorted linked list of "runnable" queues.
+     *
+     * @param head the current linked list head
+     * @param item the queue to remove
+     * @return a potentially updated linked list head
+     */
+    @VisibleForTesting
+    static @Nullable BroadcastProcessQueue removeFromRunnableList(
+            @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
+        if (head == item) {
+            head = item.runnableAtNext;
+        }
+        if (item.runnableAtNext != null) {
+            item.runnableAtNext.runnableAtPrev = item.runnableAtPrev;
+        }
+        if (item.runnableAtPrev != null) {
+            item.runnableAtPrev.runnableAtNext = item.runnableAtNext;
+        }
+        item.runnableAtNext = null;
+        item.runnableAtPrev = null;
+        return head;
     }
 
     @Override
     public String toString() {
-        return "BroadcastProcessQueue{"
-                + Integer.toHexString(System.identityHashCode(this))
-                + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+        if (mCachedToString == null) {
+            mCachedToString = "BroadcastProcessQueue{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+        }
+        return mCachedToString;
     }
 
     public String toShortString() {
-        return processName + "/" + UserHandle.formatUid(uid);
+        if (mCachedToShortString == null) {
+            mCachedToShortString = processName + "/" + UserHandle.formatUid(uid);
+        }
+        return mCachedToShortString;
     }
 
-    public void dumpLocked(@NonNull IndentingPrintWriter pw) {
+    @NeverCompile
+    public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
         if ((mActive == null) && mPending.isEmpty()) return;
 
-        pw.println(toShortString());
+        pw.print(toShortString());
+        if (isRunnable()) {
+            pw.print(" runnable at ");
+            TimeUtils.formatDuration(getRunnableAt(), now, pw);
+        } else {
+            pw.print(" not runnable");
+        }
+        pw.print(" because ");
+        pw.print(reasonToString(mRunnableAtReason));
+        if (mRunnableAtReason == REASON_BLOCKED) {
+            final SomeArgs next = mPending.peekFirst();
+            if (next != null) {
+                final BroadcastRecord r = (BroadcastRecord) next.arg1;
+                final int blockedUntilTerminalCount = next.argi2;
+                pw.print(" waiting for ");
+                pw.print(blockedUntilTerminalCount);
+                pw.print(" at ");
+                pw.print(r.terminalCount);
+                pw.print(" of ");
+                pw.print(r.receivers.size());
+            }
+        }
+        pw.println();
         pw.increaseIndent();
         if (mActive != null) {
-            pw.print("🏃 ");
-            pw.print(mActive.toShortString());
-            pw.print(' ');
-            pw.println(mActive.receivers.get(mActiveIndex));
+            dumpRecord(now, pw, mActive, mActiveIndex);
         }
         for (SomeArgs args : mPending) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            pw.print("\u3000 ");
-            pw.print(r.toShortString());
-            pw.print(' ');
-            pw.println(r.receivers.get(args.argi1));
+            dumpRecord(now, pw, r, args.argi1);
         }
         pw.decreaseIndent();
+        pw.println();
+    }
+
+    @NeverCompile
+    private void dumpRecord(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw,
+            @NonNull BroadcastRecord record, int recordIndex) {
+        TimeUtils.formatDuration(record.enqueueTime, now, pw);
+        pw.print(' ');
+        pw.println(record.toShortString());
+        pw.print("    ");
+        final int deliveryState = record.delivery[recordIndex];
+        pw.print(deliveryStateToString(deliveryState));
+        if (deliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
+            pw.print(" at ");
+            TimeUtils.formatDuration(record.scheduledTime[recordIndex], now, pw);
+        }
+        final Object receiver = record.receivers.get(recordIndex);
+        if (receiver instanceof BroadcastFilter) {
+            final BroadcastFilter filter = (BroadcastFilter) receiver;
+            pw.print(" for registered ");
+            pw.print(Integer.toHexString(System.identityHashCode(filter)));
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            final ResolveInfo info = (ResolveInfo) receiver;
+            pw.print(" for manifest ");
+            pw.print(info.activityInfo.name);
+        }
+        pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e6c446a..1e172fc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -18,16 +18,21 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.Set;
@@ -37,44 +42,47 @@
  */
 public abstract class BroadcastQueue {
     public static final String TAG = "BroadcastQueue";
+    public static final String TAG_DUMP = "broadcast_queue_dump";
 
     final @NonNull ActivityManagerService mService;
     final @NonNull Handler mHandler;
-    final @NonNull BroadcastConstants mConstants;
     final @NonNull BroadcastSkipPolicy mSkipPolicy;
     final @NonNull BroadcastHistory mHistory;
     final @NonNull String mQueueName;
 
     BroadcastQueue(@NonNull ActivityManagerService service, @NonNull Handler handler,
-            @NonNull String name, @NonNull BroadcastConstants constants,
-            @NonNull BroadcastSkipPolicy skipPolicy, @NonNull BroadcastHistory history) {
+            @NonNull String name, @NonNull BroadcastSkipPolicy skipPolicy,
+            @NonNull BroadcastHistory history) {
         mService = Objects.requireNonNull(service);
         mHandler = Objects.requireNonNull(handler);
         mQueueName = Objects.requireNonNull(name);
-        mConstants = Objects.requireNonNull(constants);
         mSkipPolicy = Objects.requireNonNull(skipPolicy);
         mHistory = Objects.requireNonNull(history);
     }
 
-    void start(@NonNull ContentResolver resolver) {
-        mConstants.startObserving(mHandler, resolver);
+    static void logw(@NonNull String msg) {
+        Slog.w(TAG, msg);
     }
 
-    static void checkState(boolean state, String msg) {
-        if (!state) {
-            Slog.wtf(TAG, msg, new Throwable());
-        }
-    }
-
-    static void logv(String msg) {
+    static void logv(@NonNull String msg) {
         Slog.v(TAG, msg);
     }
 
+    static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
+        logv(msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
+
     @Override
     public String toString() {
         return mQueueName;
     }
 
+    public abstract void start(@NonNull ContentResolver resolver);
+
     public abstract boolean isDelayBehindServices();
 
     /**
@@ -118,6 +126,9 @@
     /**
      * Signal from OS internals that the given process has just been actively
      * attached, and is ready to begin receiving broadcasts.
+     *
+     * @return if the queue performed an action on the given process, such as
+     *         dispatching a pending broadcast
      */
     @GuardedBy("mService")
     public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app);
@@ -127,7 +138,7 @@
      * an attempted start and attachment.
      */
     @GuardedBy("mService")
-    public abstract boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app);
+    public abstract void onApplicationTimeoutLocked(@NonNull ProcessRecord app);
 
     /**
      * Signal from OS internals that the given process, which had already been
@@ -135,14 +146,14 @@
      * not responding.
      */
     @GuardedBy("mService")
-    public abstract boolean onApplicationProblemLocked(@NonNull ProcessRecord app);
+    public abstract void onApplicationProblemLocked(@NonNull ProcessRecord app);
 
     /**
      * Signal from OS internals that the given process has been killed, and is
      * no longer actively running.
      */
     @GuardedBy("mService")
-    public abstract boolean onApplicationCleanupLocked(@NonNull ProcessRecord app);
+    public abstract void onApplicationCleanupLocked(@NonNull ProcessRecord app);
 
     /**
      * Signal from OS internals that the given package (or some subset of that
@@ -151,7 +162,7 @@
      */
     @GuardedBy("mService")
     public abstract boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
-            @Nullable Set<String> filterByClasses, int userId, boolean doit);
+            @Nullable Set<String> filterByClasses, int userId);
 
     /**
      * Quickly determine if this queue has broadcasts that are still waiting to
@@ -164,6 +175,16 @@
     public abstract boolean isIdleLocked();
 
     /**
+     * Quickly determine if this queue has broadcasts enqueued before the given
+     * barrier timestamp that are still waiting to be delivered.
+     *
+     * @see #waitForIdle
+     * @see #waitForBarrier
+     */
+    @GuardedBy("mService")
+    public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime);
+
+    /**
      * Wait until this queue becomes completely idle.
      * <p>
      * Any broadcasts waiting to be delivered at some point in the future will
@@ -192,10 +213,27 @@
     @GuardedBy("mService")
     public abstract @NonNull String describeStateLocked();
 
+    @GuardedBy("mService")
     public abstract void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId);
 
     @GuardedBy("mService")
     public abstract boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
-            @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
-            boolean needSep);
+            @NonNull String[] args, int opti, boolean dumpConstants, boolean dumpHistory,
+            boolean dumpAll, @Nullable String dumpPackage, boolean needSep);
+
+    /**
+     * Execute {@link #dumpLocked} and store the output into
+     * {@link DropBoxManager} for later inspection.
+     */
+    public void dumpToDropBoxLocked(@Nullable String msg) {
+        LocalServices.getService(DropBoxManagerInternal.class).addEntry(TAG_DUMP, (fd) -> {
+            try (FileOutputStream out = new FileOutputStream(fd);
+                    PrintWriter pw = new PrintWriter(out)) {
+                pw.print("Message: ");
+                pw.println(msg);
+                dumpLocked(fd, pw, null, 0, false, false, false, null, false);
+                pw.flush();
+            }
+        }, DropBoxManager.IS_TEXT);
+    }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 169f857..77300f7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -46,7 +46,6 @@
 import android.app.IApplicationThread;
 import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.usage.UsageEvents.Event;
-import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.IIntentReceiver;
@@ -70,6 +69,7 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
@@ -79,6 +79,8 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -97,6 +99,8 @@
     private static final String TAG_MU = TAG + POSTFIX_MU;
     private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
 
+    final BroadcastConstants mConstants;
+
     /**
      * If true, we can delay broadcasts while waiting services to finish in the previous
      * receiver's process.
@@ -162,7 +166,7 @@
 
     private final class BroadcastHandler extends Handler {
         public BroadcastHandler(Looper looper) {
-            super(looper, null, true);
+            super(looper, null);
         }
 
         @Override
@@ -187,20 +191,21 @@
             String name, BroadcastConstants constants, boolean allowDelayBehindServices,
             int schedGroup) {
         this(service, handler, name, constants, new BroadcastSkipPolicy(service),
-                new BroadcastHistory(), allowDelayBehindServices, schedGroup);
+                new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
     }
 
     BroadcastQueueImpl(ActivityManagerService service, Handler handler,
             String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
             BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
-        super(service, handler, name, constants, skipPolicy, history);
+        super(service, handler, name, skipPolicy, history);
         mHandler = new BroadcastHandler(handler.getLooper());
+        mConstants = constants;
         mDelayBehindServices = allowDelayBehindServices;
         mSchedGroup = schedGroup;
         mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
     }
 
-    void start(ContentResolver resolver) {
+    public void start(ContentResolver resolver) {
         mDispatcher.start();
         mConstants.startObserving(mHandler, resolver);
     }
@@ -230,6 +235,8 @@
     }
 
     public void enqueueBroadcastLocked(BroadcastRecord r) {
+        r.applySingletonPolicy(mService);
+
         final boolean replacePending = (r.intent.getFlags()
                 & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
 
@@ -424,16 +431,16 @@
         }
     }
 
-    public boolean onApplicationTimeoutLocked(ProcessRecord app) {
-        return skipCurrentOrPendingReceiverLocked(app);
+    public void onApplicationTimeoutLocked(ProcessRecord app) {
+        skipCurrentOrPendingReceiverLocked(app);
     }
 
-    public boolean onApplicationProblemLocked(ProcessRecord app) {
-        return skipCurrentOrPendingReceiverLocked(app);
+    public void onApplicationProblemLocked(ProcessRecord app) {
+        skipCurrentOrPendingReceiverLocked(app);
     }
 
-    public boolean onApplicationCleanupLocked(ProcessRecord app) {
-        return skipCurrentOrPendingReceiverLocked(app);
+    public void onApplicationCleanupLocked(ProcessRecord app) {
+        skipCurrentOrPendingReceiverLocked(app);
     }
 
     public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
@@ -599,7 +606,7 @@
         // If we're abandoning this broadcast before any receivers were actually spun up,
         // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
         if (r.nextReceiver > 0) {
-            r.duration[r.nextReceiver - 1] = elapsed;
+            r.terminalTime[r.nextReceiver - 1] = finishTime;
         }
 
         // if this receiver was slow, impose deferral policy on the app.  This will kick in
@@ -730,9 +737,10 @@
                 } catch (RemoteException ex) {
                     // Failed to call into the process. It's either dying or wedged. Kill it gently.
                     synchronized (mService) {
-                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
-                                + " (pid " + app.getPid() + "). Crashing it.");
-                        app.scheduleCrashLocked("can't deliver broadcast",
+                        final String msg = "Failed to schedule " + intent + " to " + receiver
+                                + " via " + app + ": " + ex;
+                        Slog.w(TAG, msg);
+                        app.scheduleCrashLocked(msg,
                                 CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
                     }
                     throw ex;
@@ -812,7 +820,11 @@
         try {
             if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
                     "Delivering to " + filter + " : " + r);
-            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
+            final boolean isInFullBackup = (filter.receiverList.app != null)
+                    && filter.receiverList.app.isInFullBackup();
+            final boolean isKilled = (filter.receiverList.app != null)
+                    && filter.receiverList.app.isKilled();
+            if (isInFullBackup || isKilled) {
                 // Skip delivery if full backup in progress
                 // If it's an ordered broadcast, we need to continue to the next receiver.
                 if (ordered) {
@@ -820,6 +832,7 @@
                 }
             } else {
                 r.receiverTime = SystemClock.uptimeMillis();
+                r.scheduledTime[index] = r.receiverTime;
                 maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
                 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
                 maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
@@ -1228,6 +1241,7 @@
         // Keep track of when this receiver started, and make sure there
         // is a timeout message pending to kill it if need be.
         r.receiverTime = SystemClock.uptimeMillis();
+        r.scheduledTime[recIdx] = r.receiverTime;
         if (recIdx == 0) {
             r.dispatchTime = r.receiverTime;
             r.dispatchRealTime = SystemClock.elapsedRealtime();
@@ -1377,8 +1391,11 @@
                 processCurBroadcastLocked(r, app);
                 return;
             } catch (RemoteException e) {
-                Slog.w(TAG, "Exception when sending broadcast to "
-                      + r.curComponent, e);
+                final String msg = "Failed to schedule " + r.intent + " to " + info
+                        + " via " + app + ": " + e;
+                Slog.w(TAG, msg);
+                app.scheduleCrashLocked(msg,
+                        CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
             } catch (RuntimeException e) {
                 Slog.wtf(TAG, "Failed sending broadcast to "
                         + r.curComponent + " with " + r.intent, e);
@@ -1444,7 +1461,7 @@
         return null;
     }
 
-    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+    static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
         // Only log after last receiver.
         // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
         // last BroadcastRecord of the split broadcast which has non-null resultTo.
@@ -1506,19 +1523,12 @@
         if (targetPackage == null) {
             return;
         }
-        getUsageStatsManagerInternal().reportBroadcastDispatched(
+        mService.mUsageStatsService.reportBroadcastDispatched(
                 r.callingUid, targetPackage, UserHandle.of(r.userId),
                 r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
                 mService.getUidStateLocked(targetUid));
     }
 
-    @NonNull
-    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
-        final UsageStatsManagerInternal usageStatsManagerInternal =
-                LocalServices.getService(UsageStatsManagerInternal.class);
-        return usageStatsManagerInternal;
-    }
-
     private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
         if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
             return;
@@ -1657,7 +1667,7 @@
 
             // The ANR should only be triggered if we have a process record (app is non-null)
             if (!debugging && app != null) {
-                mService.mAnrHelper.appNotResponding(app, timeoutRecord);
+                mService.appNotResponding(app, timeoutRecord);
             }
 
         } finally {
@@ -1690,18 +1700,15 @@
     }
 
     public boolean cleanupDisabledPackageReceiversLocked(
-            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+            String packageName, Set<String> filterByClasses, int userId) {
         boolean didSomething = false;
         for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
             didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
+                    packageName, filterByClasses, userId, true);
         }
 
         didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
-                filterByClasses, userId, doit);
+                filterByClasses, userId, true);
 
         return didSomething;
     }
@@ -1749,19 +1756,20 @@
         // If nothing active, we're beyond barrier
         if (isIdleLocked()) return true;
 
-        // Check if active broadcast is beyond barrier
-        final BroadcastRecord active = getActiveBroadcastLocked();
-        if (active != null && active.enqueueTime > barrierTime) {
-            return true;
+        // Check if parallel broadcasts are beyond barrier
+        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
+            if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
+                return false;
+            }
         }
 
         // Check if pending broadcast is beyond barrier
         final BroadcastRecord pending = getPendingBroadcastLocked();
-        if (pending != null && pending.enqueueTime > barrierTime) {
-            return true;
+        if ((pending != null) && pending.enqueueTime <= barrierTime) {
+            return false;
         }
 
-        return false;
+        return mDispatcher.isBeyondBarrier(barrierTime);
     }
 
     public void waitForIdle(PrintWriter pw) {
@@ -1822,6 +1830,7 @@
                 + mDispatcher.describeStateLocked();
     }
 
+    @NeverCompile
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
         proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
@@ -1838,8 +1847,10 @@
         proto.end(token);
     }
 
+    @NeverCompile
     public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+            int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
+            String dumpPackage, boolean needSep) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
         if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
                 || mPendingBroadcast != null) {
@@ -1875,8 +1886,12 @@
                 needSep = true;
             }
         }
-        mConstants.dump(pw);
-        needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+        if (dumpConstants) {
+            mConstants.dump(new IndentingPrintWriter(pw));
+        }
+        if (dumpHistory) {
+            needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+        }
         return needSep;
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b5e7b86..fe8d84f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -19,33 +19,70 @@
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
+import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
+import static com.android.server.am.BroadcastProcessQueue.reasonToString;
+import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
+import static com.android.server.am.BroadcastRecord.getReceiverPriority;
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
+import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
 import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.UidObserver;
+import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
 import android.util.IndentingPrintWriter;
+import android.util.MathUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.os.TimeoutRecord;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
+import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
+import com.android.server.am.BroadcastRecord.DeliveryState;
+
+import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -54,6 +91,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
 
 /**
  * Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
@@ -63,42 +102,54 @@
  * {@link BroadcastProcessQueue} instance. Each queue has a concept of being
  * "runnable at" a particular time in the future, which supports arbitrarily
  * pausing or delaying delivery on a per-process basis.
+ * <p>
+ * To keep things easy to reason about, there is a <em>very strong</em>
+ * preference to have broadcast interactions flow through a consistent set of
+ * methods in this specific order:
+ * <ol>
+ * <li>{@link #updateRunnableList} promotes a per-process queue to be runnable
+ * when it has relevant pending broadcasts
+ * <li>{@link #updateRunningList} promotes a runnable queue to be running and
+ * schedules delivery of the first broadcast
+ * <li>{@link #scheduleReceiverColdLocked} requests any needed cold-starts, and
+ * results are reported back via {@link #onApplicationAttachedLocked}
+ * <li>{@link #scheduleReceiverWarmLocked} requests dispatch of the currently
+ * active broadcast to a running app, and results are reported back via
+ * {@link #finishReceiverLocked}
+ * </ol>
  */
 class BroadcastQueueModernImpl extends BroadcastQueue {
     BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
-            BroadcastConstants constants) {
-        this(service, handler, constants, new BroadcastSkipPolicy(service),
-                new BroadcastHistory());
+            BroadcastConstants fgConstants, BroadcastConstants bgConstants) {
+        this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service),
+                new BroadcastHistory(fgConstants));
     }
 
     BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
-            BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
-            BroadcastHistory history) {
-        super(service, handler, "modern", constants, skipPolicy, history);
+            BroadcastConstants fgConstants, BroadcastConstants bgConstants,
+            BroadcastSkipPolicy skipPolicy, BroadcastHistory history) {
+        super(service, handler, "modern", skipPolicy, history);
+
+        // For the moment, read agnostic constants from foreground
+        mConstants = Objects.requireNonNull(fgConstants);
+        mFgConstants = Objects.requireNonNull(fgConstants);
+        mBgConstants = Objects.requireNonNull(bgConstants);
+
         mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
+
+        // We configure runnable size only once at boot; it'd be too complex to
+        // try resizing dynamically at runtime
+        mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
     }
 
-    // TODO: add support for ordered broadcasts
     // TODO: add support for replacing pending broadcasts
     // TODO: add support for merging pending broadcasts
 
-    // TODO: add trace points for debugging broadcast flows
-    // TODO: record broadcast state change timing statistics
-    // TODO: record historical broadcast statistics
+    // TODO: consider reordering foreground broadcasts within queue
 
-    // TODO: pause queues for apps involved in backup/restore
     // TODO: pause queues when background services are running
     // TODO: pause queues when processes are frozen
 
-    // TODO: clean up queues for removed apps
-
-    /**
-     * Maximum number of process queues to dispatch broadcasts to
-     * simultaneously.
-     */
-    // TODO: shift hard-coded defaults to BroadcastConstants
-    private static final int MAX_RUNNING_PROCESS_QUEUES = 4;
-
     /**
      * Map from UID to per-process broadcast queues. If a UID hosts more than
      * one process, each additional process is stored as a linked list using
@@ -111,19 +162,24 @@
     private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();
 
     /**
-     * Collection of queues which are "runnable". They're sorted by
-     * {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
+     * Head of linked list containing queues which are "runnable". They're
+     * sorted by {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
      * dispatching of longer-waiting broadcasts first.
+     *
+     * @see BroadcastProcessQueue#insertIntoRunnableList
+     * @see BroadcastProcessQueue#removeFromRunnableList
      */
-    @GuardedBy("mService")
-    private final ArrayList<BroadcastProcessQueue> mRunnable = new ArrayList<>();
+    private BroadcastProcessQueue mRunnableHead = null;
 
     /**
-     * Collection of queues which are "running". This will never be larger than
-     * {@link #MAX_RUNNING_PROCESS_QUEUES}.
+     * Array of queues which are currently "running", which may have gaps that
+     * are {@code null}.
+     *
+     * @see #getRunningSize
+     * @see #getRunningIndexOf
      */
     @GuardedBy("mService")
-    private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>();
+    private final BroadcastProcessQueue[] mRunning;
 
     /**
      * Single queue which is "running" but is awaiting a cold start to be
@@ -134,12 +190,30 @@
     private @Nullable BroadcastProcessQueue mRunningColdStart;
 
     /**
-     * Collection of latches waiting for queue to go idle.
+     * Collection of latches waiting for device to reach specific state. The
+     * first argument is a function to test for the desired state, and the
+     * second argument is the latch to release once that state is reached.
+     * <p>
+     * This is commonly used for callers that are blocked waiting for an
+     * {@link #isIdleLocked} or {@link #isBeyondBarrierLocked} to be reached,
+     * without requiring that they periodically poll for the state change.
+     * <p>
+     * Finally, the presence of any waiting latches will cause all
+     * future-runnable processes to be runnable immediately, to aid in reaching
+     * the desired state as quickly as possible.
      */
     @GuardedBy("mService")
-    private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
+    private final ArrayList<Pair<BooleanSupplier, CountDownLatch>> mWaitingFor = new ArrayList<>();
+
+    private final BroadcastConstants mConstants;
+    private final BroadcastConstants mFgConstants;
+    private final BroadcastConstants mBgConstants;
 
     private static final int MSG_UPDATE_RUNNING_LIST = 1;
+    private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
+    private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
+    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
+    private static final int MSG_CHECK_HEALTH = 5;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -152,7 +226,35 @@
         switch (msg.what) {
             case MSG_UPDATE_RUNNING_LIST: {
                 synchronized (mService) {
-                    updateRunningList();
+                    updateRunningListLocked();
+                }
+                return true;
+            }
+            case MSG_DELIVERY_TIMEOUT_SOFT: {
+                synchronized (mService) {
+                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+                }
+                return true;
+            }
+            case MSG_DELIVERY_TIMEOUT_HARD: {
+                synchronized (mService) {
+                    deliveryTimeoutHardLocked((BroadcastProcessQueue) msg.obj);
+                }
+                return true;
+            }
+            case MSG_BG_ACTIVITY_START_TIMEOUT: {
+                synchronized (mService) {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final ProcessRecord app = (ProcessRecord) args.arg1;
+                    final BroadcastRecord r = (BroadcastRecord) args.arg2;
+                    args.recycle();
+                    app.removeAllowBackgroundActivityStartsToken(r);
+                }
+                return true;
+            }
+            case MSG_CHECK_HEALTH: {
+                synchronized (mService) {
+                    checkHealthLocked();
                 }
                 return true;
             }
@@ -161,6 +263,29 @@
     };
 
     /**
+     * Return the total number of active queues contained inside
+     * {@link #mRunning}.
+     */
+    private int getRunningSize() {
+        int size = 0;
+        for (int i = 0; i < mRunning.length; i++) {
+            if (mRunning[i] != null) size++;
+        }
+        return size;
+    }
+
+    /**
+     * Return the first index of the given value contained inside
+     * {@link #mRunning}, otherwise {@code -1}.
+     */
+    private int getRunningIndexOf(@Nullable BroadcastProcessQueue test) {
+        for (int i = 0; i < mRunning.length; i++) {
+            if (mRunning[i] == test) return i;
+        }
+        return -1;
+    }
+
+    /**
      * Consider updating the list of "runnable" queues, specifically with
      * relation to the given queue.
      * <p>
@@ -170,48 +295,71 @@
      */
     @GuardedBy("mService")
     private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
-        if (mRunning.contains(queue)) {
+        if (getRunningIndexOf(queue) >= 0) {
             // Already running; they'll be reinserted into the runnable list
             // once they finish running, so no need to update them now
             return;
         }
 
-        // TODO: better optimize by using insertion sort data structure
-        mRunnable.remove(queue);
-        if (queue.isRunnable()) {
-            mRunnable.add(queue);
+        final boolean wantQueue = queue.isRunnable();
+        final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
+                || (queue.runnableAtNext != null);
+        if (wantQueue) {
+            if (inQueue) {
+                // We're in a good state, but our position within the linked
+                // list might need to move based on a runnableAt change
+                final boolean prevLower = (queue.runnableAtPrev != null)
+                        ? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
+                final boolean nextHigher = (queue.runnableAtNext != null)
+                        ? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
+                if (!prevLower || !nextHigher) {
+                    mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
+                    mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
+                }
+            } else {
+                mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
+            }
+        } else if (inQueue) {
+            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
         }
-        mRunnable.sort(null);
+
+        // If app isn't running, and there's nothing in the queue, clean up
+        if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
+            removeProcessQueue(queue.processName, queue.uid);
+        }
     }
 
     /**
      * Consider updating the list of "running" queues.
      * <p>
      * This method can promote "runnable" queues to become "running", subject to
-     * a maximum of {@link #MAX_RUNNING_PROCESS_QUEUES} warm processes and only
-     * one pending cold-start.
+     * a maximum of {@link BroadcastConstants#MAX_RUNNING_PROCESS_QUEUES} warm
+     * processes and only one pending cold-start.
      */
     @GuardedBy("mService")
-    private void updateRunningList() {
-        int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
+    private void updateRunningListLocked() {
+        int avail = mRunning.length - getRunningSize();
         if (avail == 0) return;
 
-        // If someone is waiting to go idle, everything is runnable now
-        final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
+        final int cookie = traceBegin(TAG, "updateRunningList");
+        final long now = SystemClock.uptimeMillis();
+
+        // If someone is waiting for a state, everything is runnable now
+        final boolean waitingFor = !mWaitingFor.isEmpty();
 
         // We're doing an update now, so remove any future update requests;
         // we'll repost below if needed
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
 
         boolean updateOomAdj = false;
-        final long now = SystemClock.uptimeMillis();
-        for (int i = 0; i < mRunnable.size() && avail > 0; i++) {
-            final BroadcastProcessQueue queue = mRunnable.get(i);
+        BroadcastProcessQueue queue = mRunnableHead;
+        while (queue != null && avail > 0) {
+            BroadcastProcessQueue nextQueue = queue.runnableAtNext;
             final long runnableAt = queue.getRunnableAt();
 
             // If queues beyond this point aren't ready to run yet, schedule
             // another pass when they'll be runnable
-            if (runnableAt > now && !waitingForIdle) {
+            if (runnableAt > now && !waitingFor) {
                 mLocalHandler.sendEmptyMessageAtTime(MSG_UPDATE_RUNNING_LIST, runnableAt);
                 break;
             }
@@ -228,6 +376,8 @@
                 if (mRunningColdStart == null) {
                     mRunningColdStart = queue;
                 } else {
+                    // Move to considering next runnable queue
+                    queue = nextQueue;
                     continue;
                 }
             }
@@ -236,33 +386,57 @@
                     + " from runnable to running; process is " + queue.app);
 
             // Allocate this available permit and start running!
-            mRunnable.remove(i);
-            mRunning.add(queue);
+            final int queueIndex = getRunningIndexOf(null);
+            mRunning[queueIndex] = queue;
             avail--;
-            i--;
 
-            queue.makeActiveNextPending();
+            // Remove ourselves from linked list of runnable things
+            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
 
-            // If we're already warm, schedule it; otherwise we'll wait for the
-            // cold start to circle back around
+            // Emit all trace events for this process into a consistent track
+            queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+
+            // If we're already warm, boost OOM adjust now; if cold we'll boost
+            // it after the app has been started
             if (processWarm) {
+                notifyStartedRunning(queue);
+            }
+
+            // If we're already warm, schedule next pending broadcast now;
+            // otherwise we'll wait for the cold start to circle back around
+            queue.makeActiveNextPending();
+            if (processWarm) {
+                queue.traceProcessRunningBegin();
                 scheduleReceiverWarmLocked(queue);
             } else {
+                queue.traceProcessStartingBegin();
                 scheduleReceiverColdLocked(queue);
             }
 
-            mService.enqueueOomAdjTargetLocked(queue.app);
+            // We've moved at least one process into running state above, so we
+            // need to kick off an OOM adjustment pass
             updateOomAdj = true;
+
+            // Move to considering next runnable queue
+            queue = nextQueue;
         }
 
         if (updateOomAdj) {
             mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
         }
 
-        if (waitingForIdle && isIdleLocked()) {
-            mWaitingForIdle.forEach((latch) -> latch.countDown());
-            mWaitingForIdle.clear();
+        if (waitingFor) {
+            mWaitingFor.removeIf((pair) -> {
+                if (pair.first.getAsBoolean()) {
+                    pair.second.countDown();
+                    return true;
+                } else {
+                    return false;
+                }
+            });
         }
+
+        traceEnd(TAG, cookie);
     }
 
     @Override
@@ -271,9 +445,13 @@
         if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
             // We've been waiting for this app to cold start, and it's ready
             // now; dispatch its next broadcast and clear the slot
-            scheduleReceiverWarmLocked(mRunningColdStart);
+            final BroadcastProcessQueue queue = mRunningColdStart;
             mRunningColdStart = null;
 
+            queue.traceProcessEnd();
+            queue.traceProcessRunningBegin();
+            scheduleReceiverWarmLocked(queue);
+
             // We might be willing to kick off another cold start
             enqueueUpdateRunningList();
             didSomething = true;
@@ -282,18 +460,17 @@
     }
 
     @Override
-    public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
-        return onApplicationCleanupLocked(app);
+    public void onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
+        onApplicationCleanupLocked(app);
     }
 
     @Override
-    public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
-        return onApplicationCleanupLocked(app);
+    public void onApplicationProblemLocked(@NonNull ProcessRecord app) {
+        onApplicationCleanupLocked(app);
     }
 
     @Override
-    public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
-        boolean didSomething = false;
+    public void onApplicationCleanupLocked(@NonNull ProcessRecord app) {
         if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
             // We've been waiting for this app to cold start, and it had
             // trouble; clear the slot and fail delivery below
@@ -301,7 +478,6 @@
 
             // We might be willing to kick off another cold start
             enqueueUpdateRunningList();
-            didSomething = true;
         }
 
         final BroadcastProcessQueue queue = getProcessQueue(app);
@@ -311,17 +487,24 @@
             // If queue was running a broadcast, fail it
             if (queue.isActive()) {
                 finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
-                didSomething = true;
+            }
+
+            // Skip any pending registered receivers, since the old process
+            // would never be around to receive them
+            boolean didSomething = queue.forEachMatchingBroadcast((r, i) -> {
+                return (r.receivers.get(i) instanceof BroadcastFilter);
+            }, mBroadcastConsumerSkip, true);
+            if (didSomething || queue.isEmpty()) {
+                updateRunnableList(queue);
+                enqueueUpdateRunningList();
             }
         }
-
-        return didSomething;
     }
 
     @Override
     public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
         final BroadcastProcessQueue queue = getProcessQueue(app);
-        if ((queue != null) && mRunning.contains(queue)) {
+        if ((queue != null) && getRunningIndexOf(queue) >= 0) {
             return queue.getPreferredSchedulingGroupLocked();
         }
         return ProcessList.SCHED_GROUP_UNDEFINED;
@@ -329,28 +512,96 @@
 
     @Override
     public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
-        // TODO: handle empty receivers to deliver result immediately
-        if (r.receivers == null) return;
+        r.applySingletonPolicy(mService);
+
+        final IntentFilter removeMatchingFilter = (r.options != null)
+                ? r.options.getRemoveMatchingFilter() : null;
+        if (removeMatchingFilter != null) {
+            final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
+            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+                // We only allow caller to remove broadcasts they enqueued
+                return (r.callingUid == testRecord.callingUid)
+                        && (r.userId == testRecord.userId)
+                        && removeMatching.test(testRecord.intent);
+            }, mBroadcastConsumerSkipAndCanceled, true);
+        }
+
+        if (r.isReplacePending()) {
+            // Leave the skipped broadcasts intact in queue, so that we can
+            // replace them at their current position during enqueue below
+            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+                // We only allow caller to replace broadcasts they enqueued
+                return (r.callingUid == testRecord.callingUid)
+                        && (r.userId == testRecord.userId)
+                        && r.intent.filterEquals(testRecord.intent);
+            }, mBroadcastConsumerSkipAndCanceled, false);
+        }
 
         r.enqueueTime = SystemClock.uptimeMillis();
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
 
+        int lastPriority = 0;
+        int lastPriorityIndex = 0;
+
         for (int i = 0; i < r.receivers.size(); i++) {
             final Object receiver = r.receivers.get(i);
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     getReceiverProcessName(receiver), getReceiverUid(receiver));
-            queue.enqueueBroadcast(r, i);
+
+            final int blockedUntilTerminalCount;
+            if (r.ordered) {
+                // When sending an ordered broadcast, we need to block this
+                // receiver until all previous receivers have terminated
+                blockedUntilTerminalCount = i;
+            } else if (r.prioritized) {
+                // When sending a prioritized broadcast, we only need to wait
+                // for the previous traunch of receivers to be terminated
+                final int thisPriority = getReceiverPriority(receiver);
+                if ((i == 0) || (thisPriority != lastPriority)) {
+                    lastPriority = thisPriority;
+                    lastPriorityIndex = i;
+                    blockedUntilTerminalCount = i;
+                } else {
+                    blockedUntilTerminalCount = lastPriorityIndex;
+                }
+            } else {
+                // Otherwise we don't need to block at all
+                blockedUntilTerminalCount = 0;
+            }
+
+            queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
+
+        // If nothing to dispatch, send any pending result immediately
+        if (r.receivers.isEmpty()) {
+            scheduleResultTo(r);
+        }
     }
 
+    /**
+     * Schedule the currently active broadcast on the given queue when we know
+     * the process is cold. This kicks off a cold start and will eventually call
+     * through to {@link #scheduleReceiverWarmLocked} once it's ready.
+     */
     private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
+        // Remember that active broadcast was scheduled via a cold start
+        queue.setActiveViaColdStart(true);
+
         final BroadcastRecord r = queue.getActive();
-        final Object receiver = queue.getActiveReceiver();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
+
+        // Ignore registered receivers from a previous PID
+        if (receiver instanceof BroadcastFilter) {
+            mRunningColdStart = null;
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            return;
+        }
 
         final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
         final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
@@ -368,40 +619,102 @@
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
         queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
-        if (queue.app == null) {
+        if (queue.app != null) {
+            notifyStartedRunning(queue);
+        } else {
             mRunningColdStart = null;
             finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            return;
         }
     }
 
+    /**
+     * Schedule the currently active broadcast on the given queue when we know
+     * the process is warm.
+     * <p>
+     * There is a <em>very strong</em> preference to consistently handle all
+     * results by calling through to {@link #finishReceiverLocked}, both in the
+     * case where a broadcast is handled by a remote app, and the case where the
+     * broadcast was finished locally without the remote app being involved.
+     */
     private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
         final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
-        final Object receiver = queue.getActiveReceiver();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
 
-        // TODO: schedule ANR timeout trigger event
-        // TODO: apply temp allowlist exemptions
-        // TODO: apply background activity launch exemptions
+        if (r.terminalCount == 0) {
+            r.dispatchTime = SystemClock.uptimeMillis();
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+        }
 
+        // If someone already finished this broadcast, finish immediately
+        final int oldDeliveryState = getDeliveryState(r, index);
+        if (isDeliveryStateTerminal(oldDeliveryState)) {
+            finishReceiverLocked(queue, oldDeliveryState);
+            return;
+        }
+
+        // Consider additional cases where we'd want to finish immediately
+        if (app.isInFullBackup()) {
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            return;
+        }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
             finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
             return;
         }
-
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
             finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
             return;
         }
 
+        // Ignore registered receivers from a previous PID
+        if ((receiver instanceof BroadcastFilter)
+                && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            return;
+        }
+
+        if (mService.mProcessesReady && !r.timeoutExempt) {
+            queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
+
+            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
+            mLocalHandler.sendMessageDelayed(
+                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+        }
+
+        if (r.allowBackgroundActivityStarts) {
+            app.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+
+            final long timeout = r.isForeground() ? mFgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT
+                    : mBgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT;
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = app;
+            args.arg2 = r;
+            mLocalHandler.sendMessageDelayed(
+                    Message.obtain(mLocalHandler, MSG_BG_ACTIVITY_START_TIMEOUT, args), timeout);
+        }
+
+        if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) {
+            mService.tempAllowlistUidLocked(queue.uid,
+                    r.options.getTemporaryAppAllowlistDuration(),
+                    r.options.getTemporaryAppAllowlistReasonCode(), r.toShortString(),
+                    r.options.getTemporaryAppAllowlistType(), r.callingUid);
+        }
+
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
+        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+
         final IApplicationThread thread = app.getThread();
         if (thread != null) {
             try {
-                queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
                 if (receiver instanceof BroadcastFilter) {
+                    notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
                     thread.scheduleRegisteredReceiver(
                             ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
                             r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
@@ -413,43 +726,125 @@
                         finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
                     }
                 } else {
+                    notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
                     thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
                             null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                             app.mState.getReportedProcState());
                 }
             } catch (RemoteException e) {
+                final String msg = "Failed to schedule " + r + " to " + receiver
+                        + " via " + app + ": " + e;
+                logw(msg);
+                app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
+                app.setKilled(true);
                 finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
-                synchronized (app.mService) {
-                    app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
-                }
             }
         } else {
             finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
         }
     }
 
+    /**
+     * Schedule the final {@link BroadcastRecord#resultTo} delivery for an
+     * ordered broadcast; assumes the sender is still a warm process.
+     */
+    private void scheduleResultTo(@NonNull BroadcastRecord r) {
+        if ((r.callerApp == null) || (r.resultTo == null)) return;
+        final ProcessRecord app = r.callerApp;
+        final IApplicationThread thread = app.getThread();
+        if (thread != null) {
+            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+                    app, OOM_ADJ_REASON_FINISH_RECEIVER);
+            try {
+                thread.scheduleRegisteredReceiver(r.resultTo, r.intent,
+                        r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
+                        r.userId, app.mState.getReportedProcState());
+            } catch (RemoteException e) {
+                final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
+                logw(msg);
+                app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
+            }
+        }
+        // Clear so both local and remote references can be GC'ed
+        r.resultTo = null;
+    }
+
+    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+        if (queue.app != null) {
+            // Instead of immediately triggering an ANR, extend the timeout by
+            // the amount of time the process was runnable-but-waiting; we're
+            // only willing to do this once before triggering an hard ANR
+            final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
+            final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+            mLocalHandler.sendMessageDelayed(
+                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+        } else {
+            deliveryTimeoutHardLocked(queue);
+        }
+    }
+
+    private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
+        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+    }
+
     @Override
     public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
             @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
             boolean waitForServices) {
         final BroadcastProcessQueue queue = getProcessQueue(app);
+        if ((queue == null) || !queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
+
+        final BroadcastRecord r = queue.getActive();
+        r.resultCode = resultCode;
+        r.resultData = resultData;
+        r.resultExtras = resultExtras;
+        if (!r.isNoAbort()) {
+            r.resultAbort = resultAbort;
+        }
+
+        // When the caller aborted an ordered broadcast, we mark all remaining
+        // receivers as skipped
+        if (r.ordered && r.resultAbort) {
+            for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
+                setDeliveryState(null, null, r, i, r.receivers.get(i),
+                        BroadcastRecord.DELIVERY_SKIPPED);
+            }
+        }
+
         return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
     }
 
-    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, int deliveryState) {
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState) {
         checkState(queue.isActive(), "isActive");
 
-        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
-            Slog.w(TAG, "Failed delivery of " + queue.getActive() + " to " + queue);
+        final ProcessRecord app = queue.app;
+        final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
+
+        setDeliveryState(queue, app, r, index, receiver, deliveryState);
+
+        if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
+            r.anrCount++;
+            if (app != null && !app.isDebugging()) {
+                mService.appNotResponding(queue.app, TimeoutRecord
+                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
+            }
+        } else {
+            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
         }
 
-        queue.setActiveDeliveryState(deliveryState);
+        // Even if we have more broadcasts, if we've made reasonable progress
+        // and someone else is waiting, retire ourselves to avoid starvation
+        final boolean shouldRetire = (mRunnableHead != null)
+                && (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
 
-        // TODO: cancel any outstanding ANR timeout
-        // TODO: limit number of broadcasts in a row to avoid starvation
-        // TODO: if we're the last receiver of this broadcast, record to history
-
-        if (queue.isRunnable() && queue.isProcessWarm()) {
+        if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
             // We're on a roll; move onto the next broadcast for this process
             queue.makeActiveNextPending();
             scheduleReceiverWarmLocked(queue);
@@ -457,26 +852,205 @@
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
-            mRunning.remove(queue);
-            // App is no longer running a broadcast, so update its OOM
-            // adjust during our next pass; no need for an immediate update
-            mService.enqueueOomAdjTargetLocked(queue.app);
+            queue.traceProcessEnd();
+
+            final int queueIndex = getRunningIndexOf(queue);
+            mRunning[queueIndex] = null;
             updateRunnableList(queue);
             enqueueUpdateRunningList();
+
+            // Tell other OS components that app is not actively running, giving
+            // a chance to update OOM adjustment
+            notifyStoppedRunning(queue);
             return false;
         }
     }
 
-    @Override
-    public boolean cleanupDisabledPackageReceiversLocked(String packageName,
-            Set<String> filterByClasses, int userId, boolean doit) {
-        // TODO: implement
-        return false;
+    /**
+     * Set the delivery state on the given broadcast, then apply any additional
+     * bookkeeping related to ordered broadcasts.
+     */
+    private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
+            @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+            @NonNull Object receiver, @DeliveryState int newDeliveryState) {
+        final int oldDeliveryState = getDeliveryState(r, index);
+
+        // Only apply state when we haven't already reached a terminal state;
+        // this is how we ignore racing timeout messages
+        if (!isDeliveryStateTerminal(oldDeliveryState)) {
+            r.setDeliveryState(index, newDeliveryState);
+        }
+
+        // Emit any relevant tracing results when we're changing the delivery
+        // state as part of running from a queue
+        if (queue != null) {
+            if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
+                queue.traceActiveBegin();
+            } else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED)
+                    && isDeliveryStateTerminal(newDeliveryState)) {
+                queue.traceActiveEnd();
+            }
+        }
+
+        // If we're moving into a terminal state, we might have internal
+        // bookkeeping to update for ordered broadcasts
+        if (!isDeliveryStateTerminal(oldDeliveryState)
+                && isDeliveryStateTerminal(newDeliveryState)) {
+            if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+                logw("Delivery state of " + r + " to " + receiver
+                        + " via " + app + " changed from "
+                        + deliveryStateToString(oldDeliveryState) + " to "
+                        + deliveryStateToString(newDeliveryState));
+            }
+
+            r.terminalCount++;
+            notifyFinishReceiver(queue, r, index, receiver);
+
+            // When entire ordered broadcast finished, deliver final result
+            if (r.ordered && (r.terminalCount == r.receivers.size())) {
+                scheduleResultTo(r);
+            }
+
+            // Our terminal state here might be enough for another process
+            // blocked on us to now be runnable
+            if (r.ordered || r.prioritized) {
+                for (int i = 0; i < r.receivers.size(); i++) {
+                    if (!isDeliveryStateTerminal(getDeliveryState(r, i)) || (i == index)) {
+                        final Object otherReceiver = r.receivers.get(i);
+                        final BroadcastProcessQueue otherQueue = getProcessQueue(
+                                getReceiverProcessName(otherReceiver),
+                                getReceiverUid(otherReceiver));
+                        if (otherQueue != null) {
+                            otherQueue.invalidateRunnableAt();
+                            updateRunnableList(otherQueue);
+                        }
+                    }
+                }
+                enqueueUpdateRunningList();
+            }
+        }
+    }
+
+    private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
+        return r.getDeliveryState(index);
     }
 
     @Override
-    void start(@NonNull ContentResolver resolver) {
-        super.start(resolver);
+    public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
+            @Nullable Set<String> filterByClasses, int userId) {
+        final Predicate<BroadcastProcessQueue> queuePredicate;
+        final BroadcastPredicate broadcastPredicate;
+        if (packageName != null) {
+            // Caller provided a package and user ID, so we're focused on queues
+            // belonging to a specific UID
+            final int uid = mService.mPackageManagerInt.getPackageUid(
+                    packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+            queuePredicate = (q) -> {
+                return q.uid == uid;
+            };
+
+            // If caller provided a set of classes, filter to skip only those;
+            // otherwise we skip all broadcasts
+            if (filterByClasses != null) {
+                broadcastPredicate = (r, i) -> {
+                    final Object receiver = r.receivers.get(i);
+                    if (receiver instanceof ResolveInfo) {
+                        final ActivityInfo info = ((ResolveInfo) receiver).activityInfo;
+                        return packageName.equals(info.packageName)
+                                && filterByClasses.contains(info.name);
+                    } else {
+                        return false;
+                    }
+                };
+            } else {
+                broadcastPredicate = (r, i) -> {
+                    final Object receiver = r.receivers.get(i);
+                    return packageName.equals(getReceiverPackageName(receiver));
+                };
+            }
+        } else {
+            // Caller is cleaning up an entire user ID; skip all broadcasts
+            queuePredicate = (q) -> {
+                return UserHandle.getUserId(q.uid) == userId;
+            };
+            broadcastPredicate = BROADCAST_PREDICATE_ANY;
+        }
+        return forEachMatchingBroadcast(queuePredicate, broadcastPredicate,
+                mBroadcastConsumerSkip, true);
+    }
+
+    private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
+            (q) -> true;
+    private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
+            (r, i) -> true;
+
+    /**
+     * Typical consumer that will skip the given broadcast, usually as a result
+     * of it matching a predicate.
+     */
+    private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+    };
+
+    /**
+     * Typical consumer that will both skip the given broadcast and mark it as
+     * cancelled, usually as a result of it matching a predicate.
+     */
+    private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+        r.resultCode = Activity.RESULT_CANCELED;
+        r.resultData = null;
+        r.resultExtras = null;
+    };
+
+    /**
+     * Verify that all known {@link #mProcessQueues} are in the state tested by
+     * the given {@link Predicate}.
+     */
+    private boolean testAllProcessQueues(@NonNull Predicate<BroadcastProcessQueue> test,
+            @NonNull String label, @Nullable PrintWriter pw) {
+        for (int i = 0; i < mProcessQueues.size(); i++) {
+            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+            while (leaf != null) {
+                if (!test.test(leaf)) {
+                    logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+                    return false;
+                }
+                leaf = leaf.processNameNext;
+            }
+        }
+        logv("Test " + label + " passed", pw);
+        return true;
+    }
+
+    private boolean forEachMatchingBroadcast(
+            @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
+            @NonNull BroadcastPredicate broadcastPredicate,
+            @NonNull BroadcastConsumer broadcastConsumer, boolean andRemove) {
+        boolean didSomething = false;
+        for (int i = 0; i < mProcessQueues.size(); i++) {
+            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+            while (leaf != null) {
+                if (queuePredicate.test(leaf)) {
+                    if (leaf.forEachMatchingBroadcast(broadcastPredicate,
+                            broadcastConsumer, andRemove)) {
+                        updateRunnableList(leaf);
+                        didSomething = true;
+                    }
+                }
+                leaf = leaf.processNameNext;
+            }
+        }
+        if (didSomething) {
+            enqueueUpdateRunningList();
+        }
+        return didSomething;
+    }
+
+    @Override
+    public void start(@NonNull ContentResolver resolver) {
+        mFgConstants.startObserving(mHandler, resolver);
+        mBgConstants.startObserving(mHandler, resolver);
 
         mService.registerUidObserver(new UidObserver() {
             @Override
@@ -486,24 +1060,51 @@
                     while (leaf != null) {
                         leaf.setProcessCached(cached);
                         updateRunnableList(leaf);
-                        leaf = leaf.next;
+                        leaf = leaf.processNameNext;
                     }
                     enqueueUpdateRunningList();
                 }
             }
         }, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
+
+        // Kick off periodic health checks
+        checkHealthLocked();
     }
 
     @Override
     public boolean isIdleLocked() {
-        return mRunnable.isEmpty() && mRunning.isEmpty();
+        return isIdleLocked(null);
+    }
+
+    public boolean isIdleLocked(@Nullable PrintWriter pw) {
+        return testAllProcessQueues(q -> q.isIdle(), "idle", pw);
+    }
+
+    @Override
+    public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
+        return isBeyondBarrierLocked(barrierTime, null);
+    }
+
+    public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime,
+            @Nullable PrintWriter pw) {
+        return testAllProcessQueues(q -> q.isBeyondBarrierLocked(barrierTime), "barrier", pw);
     }
 
     @Override
     public void waitForIdle(@Nullable PrintWriter pw) {
+        waitFor(() -> isIdleLocked(pw));
+    }
+
+    @Override
+    public void waitForBarrier(@Nullable PrintWriter pw) {
+        final long now = SystemClock.uptimeMillis();
+        waitFor(() -> isBeyondBarrierLocked(now, pw));
+    }
+
+    public void waitFor(@NonNull BooleanSupplier condition) {
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
-            mWaitingForIdle.add(latch);
+            mWaitingFor.add(Pair.create(condition, latch));
         }
         enqueueUpdateRunningList();
         try {
@@ -514,14 +1115,8 @@
     }
 
     @Override
-    public void waitForBarrier(@Nullable PrintWriter pw) {
-        // TODO: implement
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public String describeStateLocked() {
-        return mRunnable.size() + " runnable, " + mRunning.size() + " running";
+        return getRunningSize() + " running";
     }
 
     @Override
@@ -535,55 +1130,299 @@
         // TODO: implement
     }
 
+    /**
+     * Check overall health, confirming things are in a reasonable state and
+     * that we're not wedged. If we determine we're in an unhealthy state, dump
+     * current state once and stop future health checks to avoid spamming.
+     */
+    @VisibleForTesting
+    void checkHealthLocked() {
+        try {
+            // Verify all runnable queues are sorted
+            BroadcastProcessQueue prev = null;
+            BroadcastProcessQueue next = mRunnableHead;
+            while (next != null) {
+                checkState(next.runnableAtPrev == prev, "runnableAtPrev");
+                checkState(next.isRunnable(), "isRunnable " + next);
+                if (prev != null) {
+                    checkState(next.getRunnableAt() >= prev.getRunnableAt(),
+                            "getRunnableAt " + next + " vs " + prev);
+                }
+                prev = next;
+                next = next.runnableAtNext;
+            }
+
+            // Verify all running queues are active
+            for (BroadcastProcessQueue queue : mRunning) {
+                if (queue != null) {
+                    checkState(queue.isActive(), "isActive " + queue);
+                }
+            }
+
+            // Verify health of all known process queues
+            for (int i = 0; i < mProcessQueues.size(); i++) {
+                BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+                while (leaf != null) {
+                    leaf.checkHealthLocked();
+                    leaf = leaf.processNameNext;
+                }
+            }
+
+            // If no health issues found above, check again in the future
+            mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH, DateUtils.MINUTE_IN_MILLIS);
+
+        } catch (Exception e) {
+            // Throw up a message to indicate that something went wrong, and
+            // dump current state for later inspection
+            Slog.wtf(TAG, e);
+            dumpToDropBoxLocked(e.toString());
+        }
+    }
+
+    private int traceBegin(String trackName, String methodName) {
+        final int cookie = methodName.hashCode();
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                trackName, methodName, cookie);
+        return cookie;
+    }
+
+    private void traceEnd(String trackName, int cookie) {
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                trackName, cookie);
+    }
+
     private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
         if (!queue.isProcessWarm()) {
             queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
         }
     }
 
-    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
+    /**
+     * Inform other parts of OS that the given broadcast queue has started
+     * running, typically for internal bookkeeping.
+     */
+    private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) {
+        if (queue.app != null) {
+            queue.app.mReceivers.incrementCurReceivers();
+
+            queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+
+            // Don't bump its LRU position if it's in the background restricted.
+            if (mService.mInternal.getRestrictionLevel(
+                    queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+                mService.updateLruProcessLocked(queue.app, false, null);
+            }
+
+            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
+                    OOM_ADJ_REASON_START_RECEIVER);
+
+            mService.enqueueOomAdjTargetLocked(queue.app);
+        }
+    }
+
+    /**
+     * Inform other parts of OS that the given broadcast queue has stopped
+     * running, typically for internal bookkeeping.
+     */
+    private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
+        if (queue.app != null) {
+            // Update during our next pass; no need for an immediate update
+            mService.enqueueOomAdjTargetLocked(queue.app);
+
+            queue.app.mReceivers.decrementCurReceivers();
+        }
+    }
+
+    /**
+     * Inform other parts of OS that the given broadcast was just scheduled for
+     * a registered receiver, typically for internal bookkeeping.
+     */
+    private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
+            @NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) {
+        reportUsageStatsBroadcastDispatched(app, r);
+    }
+
+    /**
+     * Inform other parts of OS that the given broadcast was just scheduled for
+     * a manifest receiver, typically for internal bookkeeping.
+     */
+    private void notifyScheduleReceiver(@NonNull ProcessRecord app,
+            @NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) {
+        reportUsageStatsBroadcastDispatched(app, r);
+
+        final String receiverPackageName = receiver.activityInfo.packageName;
+        app.addPackage(receiverPackageName,
+                receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
+
+        final boolean targetedBroadcast = r.intent.getComponent() != null;
+        final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName);
+        if (targetedBroadcast && !targetedSelf) {
+            mService.mUsageStatsService.reportEvent(receiverPackageName,
+                    r.userId, Event.APP_COMPONENT_USED);
+        }
+
+        mService.notifyPackageUse(receiverPackageName,
+                PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+
+        mService.mPackageManagerInt.setPackageStoppedState(
+                receiverPackageName, false, r.userId);
+    }
+
+    private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
+            @NonNull BroadcastRecord r) {
+        final long idForResponseEvent = (r.options != null)
+                ? r.options.getIdForResponseEvent() : 0L;
+        if (idForResponseEvent <= 0) return;
+
+        final String targetPackage;
+        if (r.intent.getPackage() != null) {
+            targetPackage = r.intent.getPackage();
+        } else if (r.intent.getComponent() != null) {
+            targetPackage = r.intent.getComponent().getPackageName();
+        } else {
+            targetPackage = null;
+        }
+        if (targetPackage == null) return;
+
+        mService.mUsageStatsService.reportBroadcastDispatched(r.callingUid, targetPackage,
+                UserHandle.of(r.userId), idForResponseEvent, SystemClock.elapsedRealtime(),
+                mService.getUidStateLocked(app.uid));
+    }
+
+    /**
+     * Inform other parts of OS that the given broadcast was just finished,
+     * typically for internal bookkeeping.
+     */
+    private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+        // Report statistics for each individual receiver
+        final int uid = getReceiverUid(receiver);
+        final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
+        final String actionName = ActivityManagerService.getShortAction(r.intent.getAction());
+        final int receiverType = (receiver instanceof BroadcastFilter)
+                ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+                : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+        final int type;
+        if (queue == null) {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+        } else if (queue.getActiveViaColdStart()) {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+        } else {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+        }
+        // With the new per-process queues, there's no delay between being
+        // "dispatched" and "scheduled", so we report no "receive delay"
+        final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
+        final long receiveDelay = 0;
+        final long finishDelay = r.terminalTime[index] - r.scheduledTime[index];
+        FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
+                receiverType, type, dispatchDelay, receiveDelay, finishDelay);
+
+        final boolean recordFinished = (r.terminalCount == r.receivers.size());
+        if (recordFinished) {
+            mHistory.addBroadcastToHistoryLocked(r);
+
+            r.finishTime = SystemClock.uptimeMillis();
+            r.nextReceiver = r.receivers.size();
+            BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+
+            if (r.intent.getComponent() == null && r.intent.getPackage() == null
+                    && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                int manifestCount = 0;
+                int manifestSkipCount = 0;
+                for (int i = 0; i < r.receivers.size(); i++) {
+                    if (r.receivers.get(i) instanceof ResolveInfo) {
+                        manifestCount++;
+                        if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
+                            manifestSkipCount++;
+                        }
+                    }
+                }
+
+                final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
+                mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+                        manifestCount, manifestSkipCount, dispatchTime);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
         return getOrCreateProcessQueue(app.processName, app.info.uid);
     }
 
-    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
+    @VisibleForTesting
+    @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
             int uid) {
         BroadcastProcessQueue leaf = mProcessQueues.get(uid);
         while (leaf != null) {
             if (Objects.equals(leaf.processName, processName)) {
                 return leaf;
-            } else if (leaf.next == null) {
+            } else if (leaf.processNameNext == null) {
                 break;
             }
-            leaf = leaf.next;
+            leaf = leaf.processNameNext;
         }
 
-        BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
+        BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
         created.app = mService.getProcessRecordLocked(processName, uid);
 
         if (leaf == null) {
             mProcessQueues.put(uid, created);
         } else {
-            leaf.next = created;
+            leaf.processNameNext = created;
         }
         return created;
     }
 
-    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
+    @VisibleForTesting
+    @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
         return getProcessQueue(app.processName, app.info.uid);
     }
 
-    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
+    @VisibleForTesting
+    @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
         BroadcastProcessQueue leaf = mProcessQueues.get(uid);
         while (leaf != null) {
             if (Objects.equals(leaf.processName, processName)) {
                 return leaf;
             }
-            leaf = leaf.next;
+            leaf = leaf.processNameNext;
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) {
+        return removeProcessQueue(app.processName, app.info.uid);
+    }
+
+    @VisibleForTesting
+    @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName,
+            int uid) {
+        BroadcastProcessQueue prev = null;
+        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+        while (leaf != null) {
+            if (Objects.equals(leaf.processName, processName)) {
+                if (prev != null) {
+                    prev.processNameNext = leaf.processNameNext;
+                } else {
+                    if (leaf.processNameNext != null) {
+                        mProcessQueues.put(uid, leaf.processNameNext);
+                    } else {
+                        mProcessQueues.remove(uid);
+                    }
+                }
+                return leaf;
+            }
+            prev = leaf;
+            leaf = leaf.processNameNext;
         }
         return null;
     }
 
     @Override
+    @NeverCompile
     public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
         proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
@@ -592,58 +1431,70 @@
     }
 
     @Override
+    @NeverCompile
     public boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
-            @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
-            boolean needSep) {
+            @NonNull String[] args, int opti, boolean dumpConstants, boolean dumpHistory,
+            boolean dumpAll, @Nullable String dumpPackage, boolean needSep) {
         final long now = SystemClock.uptimeMillis();
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.increaseIndent();
-
         ipw.println();
+
         ipw.println("📋 Per-process queues:");
         ipw.increaseIndent();
         for (int i = 0; i < mProcessQueues.size(); i++) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
-                leaf.dumpLocked(ipw);
-                leaf = leaf.next;
+                leaf.dumpLocked(now, ipw);
+                leaf = leaf.processNameNext;
             }
         }
         ipw.decreaseIndent();
-
         ipw.println();
+
         ipw.println("🧍 Runnable:");
         ipw.increaseIndent();
-        if (mRunnable.isEmpty()) {
+        if (mRunnableHead == null) {
             ipw.println("(none)");
         } else {
-            for (BroadcastProcessQueue queue : mRunnable) {
+            BroadcastProcessQueue queue = mRunnableHead;
+            while (queue != null) {
                 TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
                 ipw.print(' ');
-                ipw.println(queue.toShortString());
+                ipw.print(reasonToString(queue.getRunnableAtReason()));
+                ipw.print(' ');
+                ipw.print(queue.toShortString());
+                ipw.println();
+                queue = queue.runnableAtNext;
             }
         }
         ipw.decreaseIndent();
-
         ipw.println();
+
         ipw.println("🏃 Running:");
         ipw.increaseIndent();
-        if (mRunning.isEmpty()) {
-            ipw.println("(none)");
-        } else {
-            for (BroadcastProcessQueue queue : mRunning) {
-                if (queue == mRunningColdStart) {
-                    ipw.print("🥶 ");
-                } else {
-                    ipw.print("\u3000 ");
-                }
+        for (BroadcastProcessQueue queue : mRunning) {
+            if ((queue != null) && (queue == mRunningColdStart)) {
+                ipw.print("🥶 ");
+            } else {
+                ipw.print("\u3000 ");
+            }
+            if (queue != null) {
                 ipw.println(queue.toShortString());
+            } else {
+                ipw.println("(none)");
             }
         }
         ipw.decreaseIndent();
+        ipw.println();
 
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+        if (dumpConstants) {
+            mConstants.dump(ipw);
+        }
+        if (dumpHistory) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+            needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+        }
         return needSep;
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 96fd362..bcc76e9 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,8 +24,12 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -47,12 +51,17 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 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;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
@@ -61,10 +70,10 @@
  * An active intent broadcast.
  */
 final class BroadcastRecord extends Binder {
-    final Intent intent;    // the original intent that generated us
-    final ComponentName targetComp; // original component name set on the intent
-    final ProcessRecord callerApp; // process that sent this
-    final String callerPackage; // who sent this
+    final @NonNull Intent intent;    // the original intent that generated us
+    final @Nullable ComponentName targetComp; // original component name set on the intent
+    final @Nullable ProcessRecord callerApp; // process that sent this
+    final @Nullable String callerPackage; // who sent this
     final @Nullable String callerFeatureId; // which feature in the package sent this
     final int callingPid;   // the pid of who sent this
     final int callingUid;   // the uid of who sent this
@@ -75,40 +84,42 @@
     final boolean pushMessage; // originated from a push message?
     final boolean pushMessageOverQuota; // originated from a push message which was over quota?
     final boolean initialSticky; // initial broadcast from register to sticky?
+    final boolean prioritized; // contains more than one priority tranche
     final int userId;       // user id this broadcast was for
-    final String resolvedType; // the resolved data type
-    final String[] requiredPermissions; // permissions the caller has required
-    final String[] excludedPermissions; // permissions to exclude
-    final String[] excludedPackages; // packages to exclude
+    final @Nullable String resolvedType; // the resolved data type
+    final @Nullable String[] requiredPermissions; // permissions the caller has required
+    final @Nullable String[] excludedPermissions; // permissions to exclude
+    final @Nullable String[] excludedPackages; // packages to exclude
     final int appOp;        // an app op that is associated with this broadcast
-    final BroadcastOptions options; // BroadcastOptions supplied by caller
-    final List receivers;   // contains BroadcastFilter and ResolveInfo
-    final int[] delivery;   // delivery state of each receiver
-    final long[] scheduledTime; // uptimeMillis when each receiver was scheduled
-    final long[] duration;   // duration a receiver took to process broadcast
-    IIntentReceiver resultTo; // who receives final result if non-null
+    final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
+    final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
+    final @DeliveryState int[] delivery;   // delivery state of each receiver
+    @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
     int splitCount;         // refcount for result callback, when split
     int splitToken;         // identifier for cross-BroadcastRecord refcount
-    long enqueueTime;       // uptimeMillis when the broadcast was enqueued
-    long enqueueRealTime;   // elapsedRealtime when the broadcast was enqueued
-    long enqueueClockTime;  // the clock time the broadcast was enqueued
-    long dispatchTime;      // when dispatch started on this set of receivers
-    long dispatchRealTime;  // elapsedRealtime when the broadcast was dispatched
-    long dispatchClockTime; // the clock time the dispatch started
-    long receiverTime;      // when current receiver started for timeouts.
-    long finishTime;        // when we finished the current receiver.
-    boolean timeoutExempt;  // true if this broadcast is not subject to receiver timeouts
+    @UptimeMillisLong       long enqueueTime;        // when broadcast enqueued
+    @ElapsedRealtimeLong    long enqueueRealTime;    // when broadcast enqueued
+    @CurrentTimeMillisLong  long enqueueClockTime;   // when broadcast enqueued
+    @UptimeMillisLong       long dispatchTime;       // when broadcast dispatch started
+    @ElapsedRealtimeLong    long dispatchRealTime;   // when broadcast dispatch started
+    @CurrentTimeMillisLong  long dispatchClockTime;  // when broadcast dispatch started
+    @UptimeMillisLong       long receiverTime;       // when receiver started for timeouts
+    @UptimeMillisLong       long finishTime;         // when broadcast finished
+    final @UptimeMillisLong long[] scheduledTime;    // when each receiver was scheduled
+    final @UptimeMillisLong long[] terminalTime;     // when each receiver was terminal
+    final boolean timeoutExempt;  // true if this broadcast is not subject to receiver timeouts
     int resultCode;         // current result code value.
-    String resultData;      // current result data value.
-    Bundle resultExtras;    // current result extra data values.
+    @Nullable String resultData;      // current result data value.
+    @Nullable Bundle resultExtras;    // current result extra data values.
     boolean resultAbort;    // current result abortBroadcast value.
     int nextReceiver;       // next receiver to be executed.
     int state;
     int anrCount;           // has this broadcast record hit any ANRs?
     int manifestCount;      // number of manifest receivers dispatched.
     int manifestSkipCount;  // number of manifest receivers skipped.
-    BroadcastQueue queue;   // the outbound queue handling this broadcast
+    int terminalCount;      // number of receivers in terminal state.
+    @Nullable BroadcastQueue queue;   // the outbound queue handling this broadcast
 
     // if set to true, app's process will be temporarily allowed to start activities from background
     // for the duration of the broadcast dispatch
@@ -122,6 +133,12 @@
     @Nullable
     final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
 
+    private @Nullable String mCachedToString;
+    private @Nullable String mCachedToShortString;
+
+    /** Empty immutable list of receivers */
+    static final List<Object> EMPTY_RECEIVERS = List.of();
+
     static final int IDLE = 0;
     static final int APP_RECEIVE = 1;
     static final int CALL_IN_RECEIVE = 2;
@@ -141,6 +158,45 @@
     /** Terminal state: failure to dispatch */
     static final int DELIVERY_FAILURE = 5;
 
+    @IntDef(flag = false, prefix = { "DELIVERY_" }, value = {
+            DELIVERY_PENDING,
+            DELIVERY_DELIVERED,
+            DELIVERY_SKIPPED,
+            DELIVERY_TIMEOUT,
+            DELIVERY_SCHEDULED,
+            DELIVERY_FAILURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeliveryState {}
+
+    static @NonNull String deliveryStateToString(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_PENDING: return "PENDING";
+            case DELIVERY_DELIVERED: return "DELIVERED";
+            case DELIVERY_SKIPPED: return "SKIPPED";
+            case DELIVERY_TIMEOUT: return "TIMEOUT";
+            case DELIVERY_SCHEDULED: return "SCHEDULED";
+            case DELIVERY_FAILURE: return "FAILURE";
+            default: return Integer.toString(deliveryState);
+        }
+    }
+
+    /**
+     * Return if the given delivery state is "terminal", where no additional
+     * delivery state changes will be made.
+     */
+    static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -152,6 +208,7 @@
     // Private refcount-management bookkeeping; start > 0
     static AtomicInteger sNextToken = new AtomicInteger(1);
 
+    @NeverCompile
     void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
         final long now = SystemClock.uptimeMillis();
 
@@ -257,17 +314,19 @@
         for (int i = 0; i < N; i++) {
             Object o = receivers.get(i);
             pw.print(prefix);
-            switch (delivery[i]) {
-                case DELIVERY_PENDING:   pw.print("Pending"); break;
-                case DELIVERY_DELIVERED: pw.print("Deliver"); break;
-                case DELIVERY_SKIPPED:   pw.print("Skipped"); break;
-                case DELIVERY_TIMEOUT:   pw.print("Timeout"); break;
-                case DELIVERY_SCHEDULED: pw.print("Schedul"); break;
-                case DELIVERY_FAILURE:   pw.print("Failure"); break;
-                default:                 pw.print("???????"); break;
+            pw.print(deliveryStateToString(delivery[i]));
+            pw.print(' ');
+            if (scheduledTime[i] != 0) {
+                pw.print("scheduled ");
+                TimeUtils.formatDuration(scheduledTime[i] - enqueueTime, pw);
+                pw.print(' ');
             }
-            pw.print(" "); TimeUtils.formatDuration(duration[i], pw);
-            pw.print(" #"); pw.print(i); pw.print(": ");
+            if (terminalTime[i] != 0) {
+                pw.print("terminal ");
+                TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
+                pw.print(' ');
+            }
+            pw.print("#"); pw.print(i); pw.print(": ");
             if (o instanceof BroadcastFilter) {
                 pw.println(o);
                 ((BroadcastFilter) o).dumpBrief(pw, p2);
@@ -295,7 +354,7 @@
             throw new NullPointerException("Can't construct with a null intent");
         }
         queue = _queue;
-        intent = _intent;
+        intent = Objects.requireNonNull(_intent);
         targetComp = _intent.getComponent();
         callerApp = _callerApp;
         callerPackage = _callerPackage;
@@ -309,10 +368,10 @@
         excludedPackages = _excludedPackages;
         appOp = _appOp;
         options = _options;
-        receivers = _receivers;
+        receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
         scheduledTime = new long[delivery.length];
-        duration = new long[delivery.length];
+        terminalTime = new long[delivery.length];
         resultTo = _resultTo;
         resultCode = _resultCode;
         resultData = _resultData;
@@ -320,6 +379,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
+        prioritized = isPrioritized(receivers);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -337,7 +397,7 @@
      * Only used by {@link #maybeStripForHistory}.
      */
     private BroadcastRecord(BroadcastRecord from, Intent newIntent) {
-        intent = newIntent;
+        intent = Objects.requireNonNull(newIntent);
         targetComp = newIntent.getComponent();
 
         callerApp = from.callerApp;
@@ -349,6 +409,7 @@
         ordered = from.ordered;
         sticky = from.sticky;
         initialSticky = from.initialSticky;
+        prioritized = from.prioritized;
         userId = from.userId;
         resolvedType = from.resolvedType;
         requiredPermissions = from.requiredPermissions;
@@ -359,7 +420,7 @@
         receivers = from.receivers;
         delivery = from.delivery;
         scheduledTime = from.scheduledTime;
-        duration = from.duration;
+        terminalTime = from.terminalTime;
         resultTo = from.resultTo;
         enqueueTime = from.enqueueTime;
         enqueueRealTime = from.enqueueRealTime;
@@ -514,12 +575,15 @@
      * Update the delivery state of the given {@link #receivers} index.
      * Automatically updates any time measurements related to state changes.
      */
-    void setDeliveryState(int index, int deliveryState) {
+    void setDeliveryState(int index, @DeliveryState int deliveryState) {
         delivery[index] = deliveryState;
 
         switch (deliveryState) {
             case DELIVERY_DELIVERED:
-                duration[index] = SystemClock.uptimeMillis() - scheduledTime[index];
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+                terminalTime[index] = SystemClock.uptimeMillis();
                 break;
             case DELIVERY_SCHEDULED:
                 scheduledTime[index] = SystemClock.uptimeMillis();
@@ -527,6 +591,10 @@
         }
     }
 
+    @DeliveryState int getDeliveryState(int index) {
+        return delivery[index];
+    }
+
     boolean isForeground() {
         return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
     }
@@ -535,6 +603,10 @@
         return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
     }
 
+    boolean isNoAbort() {
+        return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
+    }
+
     @NonNull String getHostingRecordTriggerType() {
         if (alarm) {
             return HostingRecord.TRIGGER_TYPE_ALARM;
@@ -580,6 +652,24 @@
         return (newIntent != null) ? newIntent : intent;
     }
 
+    /**
+     * Return if given receivers list has more than one traunch of priorities.
+     */
+    @VisibleForTesting
+    static boolean isPrioritized(@NonNull List<Object> receivers) {
+        int firstPriority = 0;
+        for (int i = 0; i < receivers.size(); i++) {
+            final int thisPriority = getReceiverPriority(receivers.get(i));
+            if (i == 0) {
+                firstPriority = thisPriority;
+            } else if (thisPriority != firstPriority) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
     static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
             return ((BroadcastFilter) receiver).owningUid;
@@ -588,7 +678,7 @@
         }
     }
 
-    static String getReceiverProcessName(@NonNull Object receiver) {
+    static @NonNull String getReceiverProcessName(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
             return ((BroadcastFilter) receiver).receiverList.app.processName;
         } else /* if (receiver instanceof ResolveInfo) */ {
@@ -596,6 +686,35 @@
         }
     }
 
+    static @NonNull String getReceiverPackageName(@NonNull Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).receiverList.app.info.packageName;
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            return ((ResolveInfo) receiver).activityInfo.packageName;
+        }
+    }
+
+    static int getReceiverPriority(@NonNull Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).getPriority();
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            return ((ResolveInfo) receiver).priority;
+        }
+    }
+
+    static boolean isReceiverEquals(@NonNull Object a, @NonNull Object b) {
+        if (a == b) {
+            return true;
+        } else if (a instanceof ResolveInfo && b instanceof ResolveInfo) {
+            final ResolveInfo infoA = (ResolveInfo) a;
+            final ResolveInfo infoB = (ResolveInfo) b;
+            return Objects.equals(infoA.activityInfo.packageName, infoB.activityInfo.packageName)
+                    && Objects.equals(infoA.activityInfo.name, infoB.activityInfo.name);
+        } else {
+            return false;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
@@ -645,17 +764,60 @@
         return didSomething;
     }
 
+    /**
+     * Apply special treatment to manifest receivers hosted by a singleton
+     * process, by re-targeting them at {@link UserHandle#USER_SYSTEM}.
+     */
+    void applySingletonPolicy(@NonNull ActivityManagerService service) {
+        if (receivers == null) return;
+        for (int i = 0; i < receivers.size(); i++) {
+            final Object receiver = receivers.get(i);
+            if (receiver instanceof ResolveInfo) {
+                final ResolveInfo info = (ResolveInfo) receiver;
+                boolean isSingleton = false;
+                try {
+                    isSingleton = service.isSingleton(info.activityInfo.processName,
+                            info.activityInfo.applicationInfo,
+                            info.activityInfo.name, info.activityInfo.flags);
+                } catch (SecurityException e) {
+                    BroadcastQueue.logw(e.getMessage());
+                }
+                final int receiverUid = info.activityInfo.applicationInfo.uid;
+                if (callingUid != android.os.Process.SYSTEM_UID && isSingleton
+                        && service.isValidSingletonCall(callingUid, receiverUid)) {
+                    info.activityInfo = service.getActivityInfoForUser(info.activityInfo,
+                            UserHandle.USER_SYSTEM);
+                }
+            }
+        }
+    }
+
     @Override
     public String toString() {
-        return "BroadcastRecord{"
-            + Integer.toHexString(System.identityHashCode(this))
-            + " u" + userId + " " + intent.getAction() + "}";
+        if (mCachedToString == null) {
+            String label = intent.getAction();
+            if (label == null) {
+                label = intent.toString();
+            }
+            mCachedToString = "BroadcastRecord{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " u" + userId + " " + label + "}";
+        }
+        return mCachedToString;
     }
 
     public String toShortString() {
-        return intent.getAction() + "/u" + userId;
+        if (mCachedToShortString == null) {
+            String label = intent.getAction();
+            if (label == null) {
+                label = intent.toString();
+            }
+            mCachedToShortString = label + "/u" + userId;
+        }
+        return mCachedToShortString;
     }
 
+    @NeverCompile
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
         proto.write(BroadcastRecordProto.USER_ID, userId);
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index e9b5030..60fddf0 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -137,15 +137,6 @@
             }
         }
 
-        boolean isSingleton = false;
-        try {
-            isSingleton = mService.isSingleton(info.activityInfo.processName,
-                    info.activityInfo.applicationInfo,
-                    info.activityInfo.name, info.activityInfo.flags);
-        } catch (SecurityException e) {
-            Slog.w(TAG, e.getMessage());
-            return true;
-        }
         if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
             if (ActivityManager.checkUidPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS,
@@ -216,15 +207,6 @@
             return true;
         }
 
-        // This is safe to do even if we are skipping the broadcast, and we need
-        // this information now to evaluate whether it is going to be allowed to run.
-        final int receiverUid = info.activityInfo.applicationInfo.uid;
-        // If it's a singleton, it needs to be the same app or a special app
-        if (r.callingUid != Process.SYSTEM_UID && isSingleton
-                && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
-            info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
-        }
-
         final int allowed = mService.getAppStartModeLOSP(
                 info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                 info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
diff --git a/services/core/java/com/android/server/am/BroadcastStats.java b/services/core/java/com/android/server/am/BroadcastStats.java
index fd24582..9417473 100644
--- a/services/core/java/com/android/server/am/BroadcastStats.java
+++ b/services/core/java/com/android/server/am/BroadcastStats.java
@@ -20,6 +20,8 @@
 import android.util.ArrayMap;
 import android.util.TimeUtils;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -106,6 +108,7 @@
         ve.mCount++;
     }
 
+    @NeverCompile
     public boolean dumpStats(PrintWriter pw, String prefix, String dumpPackage) {
         boolean printedSomething = false;
         ArrayList<ActionEntry> actions = new ArrayList<>(mActions.size());
@@ -155,6 +158,7 @@
         return printedSomething;
     }
 
+    @NeverCompile
     public void dumpCheckinStats(PrintWriter pw, String dumpPackage) {
         pw.print("broadcast-stats,1,");
         pw.print(mStartRealtime);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 4574302..cbf0aae 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -112,6 +112,8 @@
     private static final String ATRACE_COMPACTION_TRACK = "Compaction";
     private static final String ATRACE_FREEZER_TRACK = "Freezer";
 
+    private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+
     // Defaults for phenotype flags.
     @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
     @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
@@ -929,11 +931,13 @@
      * @param pid the target pid for which binder transactions are to be frozen
      * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
      * binder for the specificed pid.
+     * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
+     * before giving up.
      *
      * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
      * @return 0 if success, or -EAGAIN indicating there's pending transaction.
      */
-    private static native int freezeBinder(int pid, boolean freeze);
+    public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
 
     /**
      * Retrieves binder freeze info about a process.
@@ -1300,7 +1304,7 @@
         long freezeTime = opt.getFreezeUnfreezeTime();
 
         try {
-            freezeBinder(pid, false);
+            freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
         } catch (RuntimeException e) {
             Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
                     + ". Killing it");
@@ -1355,7 +1359,7 @@
             }
             Slog.d(TAG_AM, "quick sync unfreeze " + pid);
             try {
-                freezeBinder(pid, false);
+                freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
             } catch (RuntimeException e) {
                 Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
                 return;
@@ -1950,7 +1954,7 @@
                 // Freeze binder interface before the process, to flush any
                 // transactions that might be pending.
                 try {
-                    if (freezeBinder(pid, true) != 0) {
+                    if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
                         rescheduleFreeze(proc, "outstanding txns");
                         return;
                     }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbe80c8..68e5a5d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1641,11 +1641,10 @@
 
         boolean foregroundActivities = false;
         boolean hasVisibleActivities = false;
-        if (app == topApp && (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP
-                || PROCESS_STATE_CUR_TOP == PROCESS_STATE_IMPORTANT_FOREGROUND)) {
+        if (app == topApp && PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
             // The last app on the list is the foreground app.
             adj = ProcessList.FOREGROUND_APP_ADJ;
-            if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
+            if (mService.mAtmInternal.useTopSchedGroupForTopProcess()) {
                 schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
                 state.setAdjType("top-activity");
             } else {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 992d416..42bfc4c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,10 +27,12 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.getAdvertisedMem;
 import static android.os.Process.getFreeMemory;
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.startWebView;
+import static android.system.OsConstants.*;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -1532,6 +1534,7 @@
     void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
         final long homeAppMem = getMemLevel(HOME_APP_ADJ);
         final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ);
+        outInfo.advertisedMem = getAdvertisedMem();
         outInfo.availMem = getFreeMemory();
         outInfo.totalMem = getTotalMemory();
         outInfo.threshold = homeAppMem;
@@ -2709,6 +2712,50 @@
         }
     }
 
+    private static boolean freezePackageCgroup(int packageUID, boolean freeze) {
+        try {
+            Process.freezeCgroupUid(packageUID, freeze);
+        } catch (RuntimeException e) {
+            final String logtxt = freeze ? "freeze" : "unfreeze";
+            Slog.e(TAG, "Unable to " + logtxt + " cgroup uid: " + packageUID + ": " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+                                                     int packageUID) {
+        // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
+        // Since we're going to kill these, we don't need to unfreze them later.
+        // The procs list may not include all processes under the UID cgroup, but unincluded
+        // processes (forks) should not be Binder users.
+        int N = procs.size();
+        for (int i = 0; i < N; i++) {
+            final int uid = procs.get(i).first.uid;
+            final int pid = procs.get(i).first.getPid();
+            int nRetries = 0;
+            // We only freeze the cgroup of the target package, so we do not need to freeze the
+            // Binder interfaces of dependant processes in other UIDs.
+            if (pid > 0 && uid == packageUID) {
+                try {
+                    int rc;
+                    do {
+                        rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */);
+                    } while (rc == -EAGAIN && nRetries++ < 1);
+                    if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc);
+                } catch (RuntimeException e) {
+                    Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + e);
+                }
+            }
+        }
+
+        // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
+        // despite being added to a new child cgroup. The cgroups of package dependant processes are
+        // not frozen, since it's possible this would freeze processes with no dependency on the
+        // package being killed here.
+        freezePackageCgroup(packageUID, true);
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -2761,7 +2808,7 @@
                 boolean shouldAllowRestart = false;
 
                 // If no package is specified, we call all processes under the
-                // give user id.
+                // given user id.
                 if (packageName == null) {
                     if (userId != UserHandle.USER_ALL && app.userId != userId) {
                         continue;
@@ -2804,14 +2851,24 @@
             }
         }
 
+        final int packageUID = UserHandle.getUid(userId, appId);
+        final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
+                              && appId <= Process.LAST_APPLICATION_UID;
+        if (doFreeze) {
+            freezeBinderAndPackageCgroup(procs, packageUID);
+        }
+
         int N = procs.size();
         for (int i=0; i<N; i++) {
             final Pair<ProcessRecord, Boolean> proc = procs.get(i);
             removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
-                    reasonCode, subReason, reason);
+                    reasonCode, subReason, reason, !doFreeze /* async */);
         }
         killAppZygotesLocked(packageName, appId, userId, false /* force */);
         mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+        if (doFreeze) {
+            freezePackageCgroup(packageUID, false);
+        }
         return N > 0;
     }
 
@@ -2819,12 +2876,19 @@
     boolean removeProcessLocked(ProcessRecord app,
             boolean callerWillRestart, boolean allowRestart, int reasonCode, String reason) {
         return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode,
-                ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
+                ApplicationExitInfo.SUBREASON_UNKNOWN, reason, true);
     }
 
     @GuardedBy("mService")
     boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
             boolean allowRestart, int reasonCode, int subReason, String reason) {
+        return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, subReason,
+                reason, true);
+    }
+
+    @GuardedBy("mService")
+    boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
+            boolean allowRestart, int reasonCode, int subReason, String reason, boolean async) {
         final String name = app.processName;
         final int uid = app.uid;
         if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2861,7 +2925,7 @@
                     needRestart = true;
                 }
             }
-            app.killLocked(reason, reasonCode, subReason, true);
+            app.killLocked(reason, reasonCode, subReason, true, async);
             mService.handleAppDiedLocked(app, pid, willRestart, allowRestart,
                     false /* fromBinderDied */);
             if (willRestart) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 7369e8f..4c15308 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.app.ActivityManager.processStateAmToProto;
 
 import android.annotation.IntDef;
 import android.app.IApplicationThread;
@@ -318,12 +317,6 @@
                             origBase.setState(ProcessStats.STATE_NOTHING,
                                     tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
                                     pkgList.getPackageListLocked());
-                            pkgList.forEachPackage((pkgName, holder) ->
-                                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                                        mApp.uid, mApp.processName, pkgName,
-                                        processStateAmToProto(ProcessStats.STATE_NOTHING),
-                                        holder.appVersion)
-                            );
                         }
                         origBase.makeInactive();
                     }
@@ -362,12 +355,6 @@
                         origBase.setState(ProcessStats.STATE_NOTHING,
                                 tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
                                 pkgList.getPackageListLocked());
-                        pkgList.forEachPackage((pkgName, holder) ->
-                                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                                    mApp.uid, mApp.processName, pkgName,
-                                    processStateAmToProto(ProcessStats.STATE_NOTHING),
-                                    holder.appVersion)
-                        );
                     }
                     origBase.makeInactive();
                     setBaseProcessTracker(null);
diff --git a/services/core/java/com/android/server/am/ProcessReceiverRecord.java b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
index 8d3e9669..34a2b03 100644
--- a/services/core/java/com/android/server/am/ProcessReceiverRecord.java
+++ b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
@@ -34,29 +34,61 @@
      */
     private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>();
 
+    private int mCurReceiversSize;
+
     /**
      * All IIntentReceivers that are registered from this process.
      */
     private final ArraySet<ReceiverList> mReceivers = new ArraySet<>();
 
     int numberOfCurReceivers() {
-        return mCurReceivers.size();
+        return mCurReceiversSize;
     }
 
+    void incrementCurReceivers() {
+        mCurReceiversSize++;
+    }
+
+    void decrementCurReceivers() {
+        mCurReceiversSize--;
+    }
+
+    /**
+     * @deprecated we're moving towards tracking only a reference count to
+     *             improve performance.
+     */
+    @Deprecated
     BroadcastRecord getCurReceiverAt(int index) {
         return mCurReceivers.valueAt(index);
     }
 
+    /**
+     * @deprecated we're moving towards tracking only a reference count to
+     *             improve performance.
+     */
+    @Deprecated
     boolean hasCurReceiver(BroadcastRecord receiver) {
         return mCurReceivers.contains(receiver);
     }
 
+    /**
+     * @deprecated we're moving towards tracking only a reference count to
+     *             improve performance.
+     */
+    @Deprecated
     void addCurReceiver(BroadcastRecord receiver) {
         mCurReceivers.add(receiver);
+        mCurReceiversSize = mCurReceivers.size();
     }
 
+    /**
+     * @deprecated we're moving towards tracking only a reference count to
+     *             improve performance.
+     */
+    @Deprecated
     void removeCurReceiver(BroadcastRecord receiver) {
         mCurReceivers.remove(receiver);
+        mCurReceiversSize = mCurReceivers.size();
     }
 
     int numberOfReceivers() {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 07b6fcd..3b04dbb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -54,7 +54,6 @@
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.Zygote;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.wm.WindowProcessController;
 import com.android.server.wm.WindowProcessListener;
 
@@ -1057,18 +1056,30 @@
 
     @GuardedBy("mService")
     void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
-        killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+        killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
     }
 
     @GuardedBy("mService")
     void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
             boolean noisy) {
-        killLocked(reason, reason, reasonCode, subReason, noisy);
+        killLocked(reason, reason, reasonCode, subReason, noisy, true);
     }
 
     @GuardedBy("mService")
     void killLocked(String reason, String description, @Reason int reasonCode,
             @SubReason int subReason, boolean noisy) {
+        killLocked(reason, description, reasonCode, subReason, noisy, true);
+    }
+
+    @GuardedBy("mService")
+    void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
+            boolean noisy, boolean asyncKPG) {
+        killLocked(reason, reason, reasonCode, subReason, noisy, asyncKPG);
+    }
+
+    @GuardedBy("mService")
+    void killLocked(String reason, String description, @Reason int reasonCode,
+            @SubReason int subReason, boolean noisy, boolean asyncKPG) {
         if (!mKilledByAm) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
             if (reasonCode == ApplicationExitInfo.REASON_ANR
@@ -1085,7 +1096,8 @@
                 EventLog.writeEvent(EventLogTags.AM_KILL,
                         userId, mPid, processName, mState.getSetAdj(), reason);
                 Process.killProcessQuiet(mPid);
-                ProcessList.killProcessGroup(uid, mPid);
+                if (asyncKPG) ProcessList.killProcessGroup(uid, mPid);
+                else Process.killProcessGroup(uid, mPid);
             } else {
                 mPendingStart = false;
             }
@@ -1212,12 +1224,6 @@
                     long now = SystemClock.uptimeMillis();
                     baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
                             tracker.getMemFactorLocked(), now, mPkgList.getPackageListLocked());
-                    mPkgList.forEachPackage((pkgName, holder) ->
-                            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                                uid, processName, pkgName,
-                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                                holder.appVersion)
-                    );
                     if (numOfPkgs != 1) {
                         mPkgList.forEachPackageProcessStats(holder -> {
                             if (holder.state != null && holder.state != baseProcessTracker) {
@@ -1330,6 +1336,10 @@
         return mService.mAppProfiler.getCpuTimeForPid(mPid);
     }
 
+    public long getCpuDelayTime() {
+        return mService.mAppProfiler.getCpuDelayTimeForPid(mPid);
+    }
+
     @Override
     public void onStartActivity(int topProcessState, boolean setProfileProc, String packageName,
             long versionCode) {
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index ef13778..d2ef479 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -35,7 +35,6 @@
 
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.PlatformCompatCache.CachedCompatChangeId;
 
 import java.io.PrintWriter;
@@ -599,12 +598,6 @@
     @GuardedBy({"mService", "mProcLock"})
     void setReportedProcState(int repProcState) {
         mRepProcState = repProcState;
-        mApp.getPkgList().forEachPackage((pkgName, holder) ->
-                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                    mApp.uid, mApp.processName, pkgName,
-                    ActivityManager.processStateAmToProto(mRepProcState),
-                    holder.appVersion)
-        );
         mApp.getWindowProcessController().setReportedProcState(repProcState);
     }
 
@@ -620,12 +613,6 @@
                 mRepProcState = newState;
                 setCurProcState(newState);
                 setCurRawProcState(newState);
-                mApp.getPkgList().forEachPackage((pkgName, holder) ->
-                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                            mApp.uid, mApp.processName, pkgName,
-                            ActivityManager.processStateAmToProto(mRepProcState),
-                            holder.appVersion)
-                );
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 470de8c..f16347f 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -90,6 +90,7 @@
         DeviceConfig.NAMESPACE_NETD_NATIVE,
         DeviceConfig.NAMESPACE_NNAPI_NATIVE,
         DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_STATSD_NATIVE,
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index f8558e8..060e3ee 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -60,6 +60,15 @@
         { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
         { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
       ]
+    },
+    {
+      "file_patterns": ["Broadcast"],
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        { "include-filter": "com.android.server.am.BroadcastRecordTest" },
+        { "include-filter": "com.android.server.am.BroadcastQueueTest" },
+        { "include-filter": "com.android.server.am.BroadcastQueueModernImplTest" }
+      ]
     }
   ],
   "presubmit-large": [
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6775c99..226c638 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -654,7 +654,7 @@
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
         logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
                 USER_LIFECYCLE_EVENT_STATE_BEGIN);
-        // Only keep marching forward if user is actually unlocked
+        // If the user key hasn't been unlocked yet, we cannot proceed.
         if (!StorageManager.isUserKeyUnlocked(userId)) return false;
         synchronized (mLock) {
             // Do not proceed if unexpected state or a stale user
@@ -1387,7 +1387,7 @@
         int i = 0;
         for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             // NOTE: this method is setting the profiles of the current user - which is always
-            // assigned to the default display - so there's no need to pass PARENT_DISPLAY
+            // assigned to the default display
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -1430,10 +1430,7 @@
             return false;
         }
 
-        int displayId = mInjector.isUsersOnSecondaryDisplaysEnabled()
-                ? UserManagerInternal.PARENT_DISPLAY
-                : Display.DEFAULT_DISPLAY;
-        return startUserNoChecks(userId, displayId, /* foreground= */ false,
+        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
                 /* unlockListener= */ null);
     }
 
@@ -1641,7 +1638,7 @@
                 }
                 mInjector.updateUserConfiguration();
                 updateCurrentProfileIds();
-                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+                mInjector.getWindowManager().setCurrentUser(userId);
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
                 // with the option to show the user switcher on the keyguard.
@@ -1655,7 +1652,6 @@
             } else {
                 final Integer currentUserIdInt = mCurrentUserId;
                 updateCurrentProfileIds();
-                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
                 synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
@@ -1780,28 +1776,19 @@
         }
     }
 
-    boolean unlockUser(final @UserIdInt int userId, byte[] secret, IProgressListener listener) {
+    boolean unlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "unlockUser");
         EventLog.writeEvent(EventLogTags.UC_UNLOCK_USER, userId);
         final long binderToken = Binder.clearCallingIdentity();
         try {
-            return unlockUserCleared(userId, secret, listener);
+            return maybeUnlockUser(userId, listener);
         } finally {
             Binder.restoreCallingIdentity(binderToken);
         }
     }
 
-    /**
-     * Attempt to unlock user without a secret. This typically succeeds when the
-     * device doesn't have credential-encrypted storage, or when the
-     * credential-encrypted storage isn't tied to a user-provided PIN or
-     * pattern.
-     */
-    private boolean maybeUnlockUser(final @UserIdInt int userId) {
-        return unlockUserCleared(userId, null, null);
-    }
-
-    private static void notifyFinished(@UserIdInt int userId, IProgressListener listener) {
+    private static void notifyFinished(@UserIdInt int userId,
+            @Nullable IProgressListener listener) {
         if (listener == null) return;
         try {
             listener.onFinished(userId, null);
@@ -1809,8 +1796,18 @@
         }
     }
 
-    private boolean unlockUserCleared(final @UserIdInt int userId, byte[] secret,
-            IProgressListener listener) {
+    private boolean maybeUnlockUser(@UserIdInt int userId) {
+        return maybeUnlockUser(userId, null);
+    }
+
+    /**
+     * Tries to unlock the given user.
+     * <p>
+     * This will succeed only if the user's CE storage key is already unlocked or if the user
+     * doesn't have a lockscreen credential set.
+     */
+    private boolean maybeUnlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {
+
         // Delay user unlocking for headless system user mode until the system boot
         // completes. When the system boot completes, the {@link #onBootCompleted()}
         // method unlocks all started users for headless system user mode. This is done
@@ -1829,14 +1826,8 @@
 
         UserState uss;
         if (!StorageManager.isUserKeyUnlocked(userId)) {
-            final UserInfo userInfo = getUserInfo(userId);
-            final IStorageManager storageManager = mInjector.getStorageManager();
-            try {
-                // We always want to unlock user storage, even user is not started yet
-                storageManager.unlockUserKey(userId, userInfo.serialNumber, secret);
-            } catch (RemoteException | RuntimeException e) {
-                Slogf.w(TAG, "Failed to unlock: " + e.getMessage());
-            }
+            // We always want to try to unlock the user key, even if the user is not started yet.
+            mLockPatternUtils.unlockUserKeyIfUnsecured(userId);
         }
         synchronized (mLock) {
             // Register the given listener to watch for unlock progress
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictions.java b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
new file mode 100644
index 0000000..f7ccd34
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.os.PackageTagsList;
+
+import java.io.PrintWriter;
+
+/**
+ * Legacy implementation for AppOpsService's app-op restrictions (global and user)
+ * storage and access.
+ */
+public interface AppOpsRestrictions {
+    /**
+     * Set or clear a global app-op restriction for the given {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client this restriction applies to.
+     * @param code        The app-op opCode to set (or clear) a restriction for.
+     * @param restricted  {@code true} to restrict this app-op code, or {@code false} to clear an
+     *                    existing restriction.
+     * @return {@code true} if any restriction state was modified as a result of this operation
+     */
+    boolean setGlobalRestriction(Object clientToken, int code, boolean restricted);
+
+    /**
+     * Get the state of a global app-op restriction for the given {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client to get the restriction state of.
+     * @param code        The app-op code to get the restriction state of.
+     * @return the restriction state
+     */
+    boolean getGlobalRestriction(Object clientToken, int code);
+
+    /**
+     * Returns {@code true} if *any* global app-op restrictions are currently set for the given
+     * {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client to check restrictions for.
+     * @return {@code true} if any restrictions are set
+     */
+    boolean hasGlobalRestrictions(Object clientToken);
+
+    /**
+     * Clear *all* global app-op restrictions for the given {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client to clear restrictions from.
+     * @return {@code true} if any restriction state was modified as a result of this operation
+     */
+    boolean clearGlobalRestrictions(Object clientToken);
+
+    /**
+     * Set or clear a user app-op restriction for the given {@code clientToken} and {@code userId}.
+     *
+     * @param clientToken         A token identifying the client this restriction applies to.
+     * @param code                The app-op code to set (or clear) a restriction for.
+     * @param restricted          {@code true} to restrict this app-op code, or {@code false} to
+     *                            remove any existing restriction.
+     * @param excludedPackageTags A list of packages and associated attribution tags to exclude
+     *                            from this restriction. Or, if {@code null}, removes any
+     *                            exclusions from this restriction.
+     * @return {@code true} if any restriction state was modified as a result of this operation
+     */
+    boolean setUserRestriction(Object clientToken, int userId, int code, boolean restricted,
+            PackageTagsList excludedPackageTags);
+
+    /**
+     * Get the state of a user app-op restriction for the given {@code clientToken} and {@code
+     * userId}. Or, if the combination of ({{@code clientToken}, {@code userId}, @code
+     * packageName}, {@code attributionTag}) has been excluded via
+     * {@link AppOpsRestrictions#setUserRestriction}, always returns {@code false}.
+     *
+     * @param clientToken    A token identifying the client this restriction applies to.
+     * @param userId         Which userId this restriction applies to.
+     * @param code           The app-op code to get the restriction state of.
+     * @param packageName    A package name used to check for exclusions.
+     * @param attributionTag An attribution tag used to check for exclusions.
+     * @param isCheckOp      a flag that, when {@code true}, denotes that exclusions should be
+     *                       checked by (packageName) rather than (packageName, attributionTag)
+     * @return the restriction state
+     */
+    boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+            String attributionTag, boolean isCheckOp);
+
+    /**
+     * Returns {@code true} if *any* user app-op restrictions are currently set for the given
+     * {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client to check restrictions for.
+     * @return {@code true} if any restrictions are set
+     */
+    boolean hasUserRestrictions(Object clientToken);
+
+    /**
+     * Clear *all* user app-op restrictions for the given {@code clientToken}.
+     *
+     * @param clientToken A token identifying the client to clear restrictions for.
+     * @return {@code true} if any restriction state was modified as a result of this operation
+     */
+    boolean clearUserRestrictions(Object clientToken);
+
+    /**
+     * Clear *all* user app-op restrictions for the given {@code clientToken} and {@code userId}.
+     *
+     * @param clientToken A token identifying the client to clear restrictions for.
+     * @param userId      Which userId to clear restrictions for.
+     * @return {@code true} if any restriction state was modified as a result of this operation
+     */
+    boolean clearUserRestrictions(Object clientToken, Integer userId);
+
+    /**
+     * Returns the set of exclusions previously set by
+     * {@link AppOpsRestrictions#setUserRestriction} for the given {@code clientToken}
+     * and {@code userId}.
+     *
+     * @param clientToken A token identifying the client to get restriction exclusions for.
+     * @param userId      Which userId to get restriction exclusions for
+     * @return a set of user restriction exclusions
+     */
+    PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId);
+
+    /**
+     * Dump the state of appop restrictions.
+     *
+     * @param printWriter          writer to dump to.
+     * @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 dumpPackage          if not null and if dumpOp is -1, dumps watchers for the package
+     *                             name.
+     * @param showUserRestrictions include user restriction state in the output
+     */
+    void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage,
+            boolean showUserRestrictions);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
new file mode 100644
index 0000000..adfd2af
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.RequiresPermission;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.PackageTagsList;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation for AppOpsService's app-op restrictions (global and user) storage and retrieval.
+ */
+public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
+
+    private static final int UID_ANY = -2;
+
+    private Context mContext;
+    private Handler mHandler;
+    private AppOpsServiceInterface mAppOpsServiceInterface;
+
+    // Map from (Object token) to (int code) to (boolean restricted)
+    private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
+
+    // Map from (Object token) to (int userId) to (int code) to (boolean restricted)
+    private final ArrayMap<Object, SparseArray<SparseBooleanArray>> mUserRestrictions =
+            new ArrayMap<>();
+
+    // Map from (Object token) to (int userId) to (PackageTagsList packageTagsList)
+    private final ArrayMap<Object, SparseArray<PackageTagsList>>
+            mUserRestrictionExcludedPackageTags = new ArrayMap<>();
+
+    public AppOpsRestrictionsImpl(Context context, Handler handler,
+            AppOpsServiceInterface appOpsServiceInterface) {
+        mContext = context;
+        mHandler = handler;
+        mAppOpsServiceInterface = appOpsServiceInterface;
+    }
+
+    @Override
+    public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+        if (restricted) {
+            if (!mGlobalRestrictions.containsKey(clientToken)) {
+                mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
+            }
+            SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+            Objects.requireNonNull(restrictedCodes);
+            boolean changed = !restrictedCodes.get(code);
+            restrictedCodes.put(code, true);
+            return changed;
+        } else {
+            SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+            if (restrictedCodes == null) {
+                return false;
+            }
+            boolean changed = restrictedCodes.get(code);
+            restrictedCodes.delete(code);
+            if (restrictedCodes.size() == 0) {
+                mGlobalRestrictions.remove(clientToken);
+            }
+            return changed;
+        }
+    }
+
+    @Override
+    public boolean getGlobalRestriction(Object clientToken, int code) {
+        SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+        if (restrictedCodes == null) {
+            return false;
+        }
+        return restrictedCodes.get(code);
+    }
+
+    @Override
+    public boolean hasGlobalRestrictions(Object clientToken) {
+        return mGlobalRestrictions.containsKey(clientToken);
+    }
+
+    @Override
+    public boolean clearGlobalRestrictions(Object clientToken) {
+        return mGlobalRestrictions.remove(clientToken) != null;
+    }
+
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS
+    })
+    @Override
+    public boolean setUserRestriction(Object clientToken, int userId, int code,
+            boolean restricted,
+            PackageTagsList excludedPackageTags) {
+        int[] userIds = resolveUserId(userId);
+        boolean changed = false;
+        for (int i = 0; i < userIds.length; i++) {
+            changed |= putUserRestriction(clientToken, userIds[i], code, restricted);
+            changed |= putUserRestrictionExclusions(clientToken, userIds[i],
+                    excludedPackageTags);
+        }
+        return changed;
+    }
+
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS
+    })
+    private int[] resolveUserId(int userId) {
+        int[] userIds;
+        if (userId == UserHandle.USER_ALL) {
+            // TODO(b/162888972): this call is returning all users, not just live ones - we
+            // need to either fix the method called, or rename the variable
+            List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
+
+            userIds = new int[liveUsers.size()];
+            for (int i = 0; i < liveUsers.size(); i++) {
+                userIds[i] = liveUsers.get(i).id;
+            }
+        } else {
+            userIds = new int[]{userId};
+        }
+        return userIds;
+    }
+
+    @Override
+    public boolean hasUserRestrictions(Object clientToken) {
+        return mUserRestrictions.containsKey(clientToken);
+    }
+
+    private boolean getUserRestriction(Object clientToken, int userId, int code) {
+        SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+                mUserRestrictions.get(clientToken);
+        if (userIdRestrictedCodes == null) {
+            return false;
+        }
+        SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+        if (restrictedCodes == null) {
+            return false;
+        }
+        return restrictedCodes.get(code);
+    }
+
+    @Override
+    public boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+            String attributionTag, boolean isCheckOp) {
+        boolean restricted = getUserRestriction(clientToken, userId, code);
+        if (!restricted) {
+            return false;
+        }
+
+        PackageTagsList perUserExclusions = getUserRestrictionExclusions(clientToken, userId);
+        if (perUserExclusions == null) {
+            return true;
+        }
+
+        // TODO (b/240617242) add overload for checkOp to support attribution tags
+        if (isCheckOp) {
+            return !perUserExclusions.includes(packageName);
+        }
+        return !perUserExclusions.contains(packageName, attributionTag);
+    }
+
+    @Override
+    public boolean clearUserRestrictions(Object clientToken) {
+        boolean changed = false;
+        SparseBooleanArray allUserRestrictedCodes = collectAllUserRestrictedCodes(clientToken);
+        changed |= mUserRestrictions.remove(clientToken) != null;
+        changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
+        notifyAllUserRestrictions(allUserRestrictedCodes);
+        return changed;
+    }
+
+    private SparseBooleanArray collectAllUserRestrictedCodes(Object clientToken) {
+        SparseBooleanArray allRestrictedCodes = new SparseBooleanArray();
+        SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(clientToken);
+        if (userIdRestrictedCodes == null) {
+            return allRestrictedCodes;
+        }
+        int userIdRestrictedCodesSize = userIdRestrictedCodes.size();
+        for (int i = 0; i < userIdRestrictedCodesSize; i++) {
+            SparseBooleanArray restrictedCodes = userIdRestrictedCodes.valueAt(i);
+            int restrictedCodesSize = restrictedCodes.size();
+            for (int j = 0; j < restrictedCodesSize; j++) {
+                int code = restrictedCodes.keyAt(j);
+                allRestrictedCodes.put(code, true);
+            }
+        }
+        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(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+        }
+    }
+
+    @Override
+    public boolean clearUserRestrictions(Object clientToken, Integer userId) {
+        boolean changed = false;
+
+        SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+                mUserRestrictions.get(clientToken);
+        if (userIdRestrictedCodes != null) {
+            changed |= userIdRestrictedCodes.contains(userId);
+            userIdRestrictedCodes.remove(userId);
+            if (userIdRestrictedCodes.size() == 0) {
+                mUserRestrictions.remove(clientToken);
+            }
+        }
+
+        SparseArray<PackageTagsList> userIdPackageTags =
+                mUserRestrictionExcludedPackageTags.get(clientToken);
+        if (userIdPackageTags != null) {
+            changed |= userIdPackageTags.contains(userId);
+            userIdPackageTags.remove(userId);
+            if (userIdPackageTags.size() == 0) {
+                mUserRestrictionExcludedPackageTags.remove(clientToken);
+            }
+        }
+
+        return changed;
+    }
+
+    private boolean putUserRestriction(Object token, int userId, int code, boolean restricted) {
+        boolean changed = false;
+        if (restricted) {
+            if (!mUserRestrictions.containsKey(token)) {
+                mUserRestrictions.put(token, new SparseArray<>());
+            }
+            SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+            Objects.requireNonNull(userIdRestrictedCodes);
+
+            if (!userIdRestrictedCodes.contains(userId)) {
+                userIdRestrictedCodes.put(userId, new SparseBooleanArray());
+            }
+            SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+
+            changed = !restrictedCodes.get(code);
+            restrictedCodes.put(code, restricted);
+        } else {
+            SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+            if (userIdRestrictedCodes == null) {
+                return false;
+            }
+            SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+            if (restrictedCodes == null) {
+                return false;
+            }
+            changed = restrictedCodes.get(code);
+            restrictedCodes.delete(code);
+            if (restrictedCodes.size() == 0) {
+                userIdRestrictedCodes.remove(userId);
+            }
+            if (userIdRestrictedCodes.size() == 0) {
+                mUserRestrictions.remove(token);
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId) {
+        SparseArray<PackageTagsList> userIdPackageTags =
+                mUserRestrictionExcludedPackageTags.get(clientToken);
+        if (userIdPackageTags == null) {
+            return null;
+        }
+        return userIdPackageTags.get(userId);
+    }
+
+    private boolean putUserRestrictionExclusions(Object token, int userId,
+            PackageTagsList excludedPackageTags) {
+        boolean addingExclusions = excludedPackageTags != null && !excludedPackageTags.isEmpty();
+        if (addingExclusions) {
+            if (!mUserRestrictionExcludedPackageTags.containsKey(token)) {
+                mUserRestrictionExcludedPackageTags.put(token, new SparseArray<>());
+            }
+            SparseArray<PackageTagsList> userIdExcludedPackageTags =
+                    mUserRestrictionExcludedPackageTags.get(token);
+            Objects.requireNonNull(userIdExcludedPackageTags);
+
+            userIdExcludedPackageTags.put(userId, excludedPackageTags);
+            return true;
+        } else {
+            SparseArray<PackageTagsList> userIdExclusions =
+                    mUserRestrictionExcludedPackageTags.get(token);
+            if (userIdExclusions == null) {
+                return false;
+            }
+            boolean changed = userIdExclusions.get(userId) != null;
+            userIdExclusions.remove(userId);
+            if (userIdExclusions.size() == 0) {
+                mUserRestrictionExcludedPackageTags.remove(token);
+            }
+            return changed;
+        }
+    }
+
+    @Override
+    public void dumpRestrictions(PrintWriter pw, int code, String dumpPackage,
+            boolean showUserRestrictions) {
+        final int globalRestrictionCount = mGlobalRestrictions.size();
+        for (int i = 0; i < globalRestrictionCount; i++) {
+            Object token = mGlobalRestrictions.keyAt(i);
+            SparseBooleanArray restrictedOps = mGlobalRestrictions.valueAt(i);
+
+            pw.println("  Global restrictions for token " + token + ":");
+            StringBuilder restrictedOpsValue = new StringBuilder();
+            restrictedOpsValue.append("[");
+            final int restrictedOpCount = restrictedOps.size();
+            for (int j = 0; j < restrictedOpCount; j++) {
+                if (restrictedOpsValue.length() > 1) {
+                    restrictedOpsValue.append(", ");
+                }
+                restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.keyAt(j)));
+            }
+            restrictedOpsValue.append("]");
+            pw.println("      Restricted ops: " + restrictedOpsValue);
+        }
+
+        if (!showUserRestrictions) {
+            return;
+        }
+
+        final int userRestrictionCount = mUserRestrictions.size();
+        for (int i = 0; i < userRestrictionCount; i++) {
+            Object token = mUserRestrictions.keyAt(i);
+            SparseArray<SparseBooleanArray> perUserRestrictions = mUserRestrictions.get(token);
+            SparseArray<PackageTagsList> perUserExcludedPackageTags =
+                    mUserRestrictionExcludedPackageTags.get(token);
+
+            boolean printedTokenHeader = false;
+
+            final int restrictionCount = perUserRestrictions != null
+                    ? perUserRestrictions.size() : 0;
+            if (restrictionCount > 0 && dumpPackage == null) {
+                boolean printedOpsHeader = false;
+                for (int j = 0; j < restrictionCount; j++) {
+                    int userId = perUserRestrictions.keyAt(j);
+                    SparseBooleanArray restrictedOps = perUserRestrictions.valueAt(j);
+                    if (restrictedOps == null) {
+                        continue;
+                    }
+                    if (code >= 0 && !restrictedOps.get(code)) {
+                        continue;
+                    }
+                    if (!printedTokenHeader) {
+                        pw.println("  User restrictions for token " + token + ":");
+                        printedTokenHeader = true;
+                    }
+                    if (!printedOpsHeader) {
+                        pw.println("      Restricted ops:");
+                        printedOpsHeader = true;
+                    }
+                    StringBuilder restrictedOpsValue = new StringBuilder();
+                    restrictedOpsValue.append("[");
+                    final int restrictedOpCount = restrictedOps.size();
+                    for (int k = 0; k < restrictedOpCount; k++) {
+                        int restrictedOp = restrictedOps.keyAt(k);
+                        if (restrictedOpsValue.length() > 1) {
+                            restrictedOpsValue.append(", ");
+                        }
+                        restrictedOpsValue.append(AppOpsManager.opToName(restrictedOp));
+                    }
+                    restrictedOpsValue.append("]");
+                    pw.print("        ");
+                    pw.print("user: ");
+                    pw.print(userId);
+                    pw.print(" restricted ops: ");
+                    pw.println(restrictedOpsValue);
+                }
+            }
+
+            final int excludedPackageCount = perUserExcludedPackageTags != null
+                    ? perUserExcludedPackageTags.size() : 0;
+            if (excludedPackageCount > 0 && code < 0) {
+                IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+                ipw.increaseIndent();
+                boolean printedPackagesHeader = false;
+                for (int j = 0; j < excludedPackageCount; j++) {
+                    int userId = perUserExcludedPackageTags.keyAt(j);
+                    PackageTagsList packageNames =
+                            perUserExcludedPackageTags.valueAt(j);
+                    if (packageNames == null) {
+                        continue;
+                    }
+                    boolean hasPackage;
+                    if (dumpPackage != null) {
+                        hasPackage = packageNames.includes(dumpPackage);
+                    } else {
+                        hasPackage = true;
+                    }
+                    if (!hasPackage) {
+                        continue;
+                    }
+                    if (!printedTokenHeader) {
+                        ipw.println("User restrictions for token " + token + ":");
+                        printedTokenHeader = true;
+                    }
+
+                    ipw.increaseIndent();
+                    if (!printedPackagesHeader) {
+                        ipw.println("Excluded packages:");
+                        printedPackagesHeader = true;
+                    }
+
+                    ipw.increaseIndent();
+                    ipw.print("user: ");
+                    ipw.print(userId);
+                    ipw.println(" packages: ");
+
+                    ipw.increaseIndent();
+                    packageNames.dump(ipw);
+
+                    ipw.decreaseIndent();
+                    ipw.decreaseIndent();
+                    ipw.decreaseIndent();
+                }
+                ipw.decreaseIndent();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index af73c2b..bc650ad 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,7 +33,6 @@
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.NoteOpEvent;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
@@ -45,7 +44,6 @@
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
 import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.RestrictionBypass;
@@ -57,7 +55,6 @@
 import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.extractFlagsFromKey;
 import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.makeKey;
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
 import static android.app.AppOpsManager.opRestrictsRead;
@@ -71,7 +68,6 @@
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
 import android.Manifest;
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -101,7 +97,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
-import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
@@ -110,6 +105,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -122,19 +118,14 @@
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.storage.StorageManagerInternal;
 import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.IndentingPrintWriter;
 import android.util.KeyValueListParser;
-import android.util.LongSparseArray;
 import android.util.Pair;
-import android.util.Pools;
-import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -198,7 +189,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Scanner;
 import java.util.Set;
@@ -241,14 +231,22 @@
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
-    /** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */
+    /**
+     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+     * objects
+     */
     @GuardedBy("this")
-    private final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool();
+    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
 
-    /** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */
+    /**
+     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+     * new objects
+     */
     @GuardedBy("this")
-    private final InProgressStartOpEventPool mInProgressStartOpEventPool =
-            new InProgressStartOpEventPool();
+    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+                    MAX_UNUSED_POOLED_OBJECTS);
 
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
@@ -367,6 +365,9 @@
     /** Interface for app-op modes.*/
     @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
 
+    /** Interface for app-op restrictions.*/
+    @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
     private AppOpsUidStateTracker mUidStateTracker;
 
     /** Hands the definition of foreground and uid states */
@@ -374,69 +375,22 @@
     public AppOpsUidStateTracker getUidStateTracker() {
         if (mUidStateTracker == null) {
             mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class), mHandler,
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsService.this) {
+                            r.run();
+                        }
+                    },
                     Clock.SYSTEM_CLOCK, mConstants);
 
-            mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged);
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
         }
         return mUidStateTracker;
     }
 
     /**
-     * An unsynchronized pool of {@link OpEventProxyInfo} objects.
-     */
-    private class OpEventProxyInfoPool extends SimplePool<OpEventProxyInfo> {
-        OpEventProxyInfoPool() {
-            super(MAX_UNUSED_POOLED_OBJECTS);
-        }
-
-        OpEventProxyInfo acquire(@IntRange(from = 0) int uid, @Nullable String packageName,
-                @Nullable String attributionTag) {
-            OpEventProxyInfo recycled = acquire();
-            if (recycled != null) {
-                recycled.reinit(uid, packageName, attributionTag);
-                return recycled;
-            }
-
-            return new OpEventProxyInfo(uid, packageName, attributionTag);
-        }
-    }
-
-    /**
-     * An unsynchronized pool of {@link InProgressStartOpEvent} objects.
-     */
-    private class InProgressStartOpEventPool extends SimplePool<InProgressStartOpEvent> {
-        InProgressStartOpEventPool() {
-            super(MAX_UNUSED_POOLED_OBJECTS);
-        }
-
-        InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
-                @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
-                int attributionFlags, int attributionChainId) throws RemoteException {
-
-            InProgressStartOpEvent recycled = acquire();
-
-            OpEventProxyInfo proxyInfo = null;
-            if (proxyUid != Process.INVALID_UID) {
-                proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                        proxyAttributionTag);
-            }
-
-            if (recycled != null) {
-                recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
-                        uidState, flags, proxyInfo,  attributionFlags, attributionChainId,
-                        mOpEventProxyInfoPool);
-                return recycled;
-            }
-
-            return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
-                    onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
-        }
-    }
-
-    /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
      * holding the AppOpsService lock.
@@ -652,740 +606,6 @@
         }
     }
 
-    /** A in progress startOp->finishOp event */
-    private static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
-        /** Wall clock time of startOp event (not monotonic) */
-        private long mStartTime;
-
-        /** Elapsed time since boot of startOp event */
-        private long mStartElapsedTime;
-
-        /** Id of the client that started the event */
-        private @NonNull IBinder mClientId;
-
-        /** The attribution tag for this operation */
-        private @Nullable String mAttributionTag;
-
-        /** To call when client dies */
-        private @NonNull Runnable mOnDeath;
-
-        /** uidstate used when calling startOp */
-        private @AppOpsManager.UidState int mUidState;
-
-        /** Proxy information of the startOp event */
-        private @Nullable OpEventProxyInfo mProxy;
-
-        /** Proxy flag information */
-        private @OpFlags int mFlags;
-
-        /** How many times the op was started but not finished yet */
-        int numUnfinishedStarts;
-
-        /** The attribution flags related to this event */
-        private @AttributionFlags int mAttributionFlags;
-
-        /** The id of the attribution chain this even is a part of */
-        private int mAttributionChainId;
-
-        /**
-         * Create a new {@link InProgressStartOpEvent}.
-         *
-         * @param startTime The time {@link #startOperation} was called
-         * @param startElapsedTime The elapsed time when {@link #startOperation} was called
-         * @param clientId The client id of the caller of {@link #startOperation}
-         * @param attributionTag The attribution tag for the operation.
-         * @param onDeath The code to execute on client death
-         * @param uidState The uidstate of the app {@link #startOperation} was called for
-         * @param attributionFlags the attribution flags for this operation.
-         * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxy The proxy information, if {@link #startProxyOperation} was called
-         * @param flags The trusted/nontrusted/self flags.
-         *
-         * @throws RemoteException If the client is dying
-         */
-        private InProgressStartOpEvent(long startTime, long startElapsedTime,
-                @NonNull IBinder clientId, @Nullable String attributionTag,
-                @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
-                @Nullable OpEventProxyInfo proxy, @OpFlags int flags,
-                @AttributionFlags int attributionFlags, int attributionChainId)
-                throws RemoteException {
-            mStartTime = startTime;
-            mStartElapsedTime = startElapsedTime;
-            mClientId = clientId;
-            mAttributionTag = attributionTag;
-            mOnDeath = onDeath;
-            mUidState = uidState;
-            mProxy = proxy;
-            mFlags = flags;
-            mAttributionFlags = attributionFlags;
-            mAttributionChainId = attributionChainId;
-
-            clientId.linkToDeath(this, 0);
-        }
-
-        /** Clean up event */
-        public void finish() {
-            try {
-                mClientId.unlinkToDeath(this, 0);
-            } catch (NoSuchElementException e) {
-                // Either not linked, or already unlinked. Either way, nothing to do.
-            }
-        }
-
-        @Override
-        public void binderDied() {
-            mOnDeath.run();
-        }
-
-        /**
-         * Reinit existing object with new state.
-         *
-         * @param startTime The time {@link #startOperation} was called
-         * @param startElapsedTime The elapsed time when {@link #startOperation} was called
-         * @param clientId The client id of the caller of {@link #startOperation}
-         * @param attributionTag The attribution tag for this operation.
-         * @param onDeath The code to execute on client death
-         * @param uidState The uidstate of the app {@link #startOperation} was called for
-         * @param flags The flags relating to the proxy
-         * @param proxy The proxy information, if {@link #startProxyOperation} was called
-         * @param attributionFlags the attribution flags for this operation.
-         * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to
-         *
-         * @throws RemoteException If the client is dying
-         */
-        public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
-                @Nullable String attributionTag, @NonNull Runnable onDeath,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags,
-                int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
-        ) throws RemoteException {
-            mStartTime = startTime;
-            mStartElapsedTime = startElapsedTime;
-            mClientId = clientId;
-            mAttributionTag = attributionTag;
-            mOnDeath = onDeath;
-            mUidState = uidState;
-            mFlags = flags;
-
-            if (mProxy != null) {
-                proxyPool.release(mProxy);
-            }
-            mProxy = proxy;
-            mAttributionFlags = attributionFlags;
-            mAttributionChainId = attributionChainId;
-
-            clientId.linkToDeath(this, 0);
-        }
-
-        /** @return Wall clock time of startOp event */
-        public long getStartTime() {
-            return mStartTime;
-        }
-
-        /** @return Elapsed time since boot of startOp event */
-        public long getStartElapsedTime() {
-            return mStartElapsedTime;
-        }
-
-        /** @return Id of the client that started the event */
-        public @NonNull IBinder getClientId() {
-            return mClientId;
-        }
-
-        /** @return uidstate used when calling startOp */
-        public @AppOpsManager.UidState int getUidState() {
-            return mUidState;
-        }
-
-        /** @return proxy tag for the access */
-        public @Nullable OpEventProxyInfo getProxy() {
-            return mProxy;
-        }
-
-        /** @return flags used for the access */
-        public @OpFlags int getFlags() {
-            return mFlags;
-        }
-
-        /** @return attributoin flags used for the access */
-        public @AttributionFlags int getAttributionFlags() {
-            return mAttributionFlags;
-        }
-
-        /** @return attribution chain id for the access */
-        public int getAttributionChainId() {
-            return mAttributionChainId;
-        }
-    }
-
-    private final class AttributedOp {
-        public final @Nullable String tag;
-        public final @NonNull Op parent;
-
-        /**
-         * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
-         *
-         * <p>Key is {@link AppOpsManager#makeKey}
-         */
-        @GuardedBy("AppOpsService.this")
-        private @Nullable LongSparseArray<NoteOpEvent> mAccessEvents;
-
-        /**
-         * Last rejected accesses for each uidState/opFlag combination
-         *
-         * <p>Key is {@link AppOpsManager#makeKey}
-         */
-        @GuardedBy("AppOpsService.this")
-        private @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
-
-        /**
-         * Currently in progress startOp events
-         *
-         * <p>Key is clientId
-         */
-        @GuardedBy("AppOpsService.this")
-        private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
-
-        /**
-         * Currently paused startOp events
-         *
-         * <p>Key is clientId
-         */
-        @GuardedBy("AppOpsService.this")
-        private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
-
-        AttributedOp(@Nullable String tag, @NonNull Op parent) {
-            this.tag = tag;
-            this.parent = parent;
-        }
-
-        /**
-         * Update state when noteOp was rejected or startOp->finishOp event finished
-         *
-         * @param proxyUid The uid of the proxy
-         * @param proxyPackageName The package name of the proxy
-         * @param proxyAttributionTag the attributionTag in the proxies package
-         * @param uidState UID state of the app noteOp/startOp was called for
-         * @param flags OpFlags of the call
-         */
-        public void accessed(int proxyUid, @Nullable String proxyPackageName,
-                @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
-                @OpFlags int flags) {
-            long accessTime = System.currentTimeMillis();
-            accessed(accessTime, -1, proxyUid, proxyPackageName,
-                    proxyAttributionTag, uidState, flags);
-
-            mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
-                    tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
-                    AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
-        }
-
-        /**
-         * Add an access that was previously collected.
-         *
-         * @param noteTime The time of the event
-         * @param duration The duration of the event
-         * @param proxyUid The uid of the proxy
-         * @param proxyPackageName The package name of the proxy
-         * @param proxyAttributionTag the attributionTag in the proxies package
-         * @param uidState UID state of the app noteOp/startOp was called for
-         * @param flags OpFlags of the call
-         */
-        public void accessed(long noteTime, long duration, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags) {
-            long key = makeKey(uidState, flags);
-
-            if (mAccessEvents == null) {
-                mAccessEvents = new LongSparseArray<>(1);
-            }
-
-            OpEventProxyInfo proxyInfo = null;
-            if (proxyUid != Process.INVALID_UID) {
-                proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                        proxyAttributionTag);
-            }
-
-            NoteOpEvent existingEvent = mAccessEvents.get(key);
-            if (existingEvent != null) {
-                existingEvent.reinit(noteTime, duration, proxyInfo, mOpEventProxyInfoPool);
-            } else {
-                mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo));
-            }
-        }
-
-        /**
-         * Update state when noteOp/startOp was rejected.
-         *
-         * @param uidState UID state of the app noteOp is called for
-         * @param flags OpFlags of the call
-         */
-        public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
-            rejected(System.currentTimeMillis(), uidState, flags);
-
-            mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName,
-                    tag, uidState, flags);
-        }
-
-        /**
-         * Add an rejection that was previously collected
-         *
-         * @param noteTime The time of the event
-         * @param uidState UID state of the app noteOp/startOp was called for
-         * @param flags OpFlags of the call
-         */
-        public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
-                @OpFlags int flags) {
-            long key = makeKey(uidState, flags);
-
-            if (mRejectEvents == null) {
-                mRejectEvents = new LongSparseArray<>(1);
-            }
-
-            // We do not collect proxy information for rejections yet
-            NoteOpEvent existingEvent = mRejectEvents.get(key);
-            if (existingEvent != null) {
-                existingEvent.reinit(noteTime, -1, null, mOpEventProxyInfoPool);
-            } else {
-                mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null));
-            }
-        }
-
-        /**
-         * Update state when start was called
-         *
-         * @param clientId Id of the startOp caller
-         * @param proxyUid The UID of the proxy app
-         * @param proxyPackageName The package name of the proxy app
-         * @param proxyAttributionTag The attribution tag of the proxy app
-         * @param uidState UID state of the app startOp is called for
-         * @param flags The proxy flags
-         * @param attributionFlags The attribution flags associated with this operation.
-         * @param attributionChainId The if of the attribution chain this operations is a part of.
-         */
-        public void started(@NonNull IBinder clientId, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
-                int attributionFlags, int attributionChainId) throws RemoteException {
-            started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags,
-                    attributionChainId);
-        }
-
-        private void started(@NonNull IBinder clientId, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags,
-                int attributionChainId) throws RemoteException {
-            startedOrPaused(clientId, proxyUid, proxyPackageName,
-                    proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
-                    /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
-        }
-
-        private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags
-                int attributionFlags, int attributionChainId) throws RemoteException {
-            if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
-                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
-                        tag, true, attributionFlags, attributionChainId);
-            }
-
-            if (isStarted && mInProgressEvents == null) {
-                mInProgressEvents = new ArrayMap<>(1);
-            } else if (!isStarted && mPausedInProgressEvents == null) {
-                mPausedInProgressEvents = new ArrayMap<>(1);
-            }
-            ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
-                    ? mInProgressEvents : mPausedInProgressEvents;
-
-            long startTime = System.currentTimeMillis();
-            InProgressStartOpEvent event = events.get(clientId);
-            if (event == null) {
-                event = mInProgressStartOpEventPool.acquire(startTime,
-                        SystemClock.elapsedRealtime(), clientId, tag,
-                        PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
-                        proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
-                        attributionFlags, attributionChainId);
-                events.put(clientId, event);
-            } else {
-                if (uidState != event.mUidState) {
-                    onUidStateChanged(uidState);
-                }
-            }
-
-            event.numUnfinishedStarts++;
-
-            if (isStarted) {
-                mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                        parent.packageName, tag, uidState, flags, startTime, attributionFlags,
-                        attributionChainId);
-            }
-        }
-
-        /**
-         * Update state when finishOp was called. Will finish started ops, and delete paused ops.
-         *
-         * @param clientId Id of the finishOp caller
-         */
-        public void finished(@NonNull IBinder clientId) {
-            finished(clientId, true);
-        }
-
-        private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
-            finishOrPause(clientId, triggerCallbackIfNeeded, false);
-        }
-
-        /**
-         * Update state when paused or finished is called. If pausing, it records the op as
-         * stopping in the HistoricalRegistry, but does not delete it.
-         */
-        private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
-                boolean isPausing) {
-            int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
-            if (indexOfToken < 0) {
-                finishPossiblyPaused(clientId, isPausing);
-                return;
-            }
-
-            InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
-            if (!isPausing) {
-                event.numUnfinishedStarts--;
-            }
-            // If we are pausing, create a NoteOpEvent, but don't change the InProgress event
-            if (event.numUnfinishedStarts == 0 || isPausing) {
-                if (!isPausing) {
-                    event.finish();
-                    mInProgressEvents.removeAt(indexOfToken);
-                }
-
-                if (mAccessEvents == null) {
-                    mAccessEvents = new LongSparseArray<>(1);
-                }
-
-                OpEventProxyInfo proxyCopy = event.getProxy() != null
-                        ? new OpEventProxyInfo(event.getProxy()) : null;
-
-                long accessDurationMillis =
-                        SystemClock.elapsedRealtime() - event.getStartElapsedTime();
-                NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(),
-                        accessDurationMillis, proxyCopy);
-                mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
-                        finishedEvent);
-
-                mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
-                        parent.packageName, tag, event.getUidState(),
-                        event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
-                        event.getAttributionFlags(), event.getAttributionChainId());
-
-                if (!isPausing) {
-                    mInProgressStartOpEventPool.release(event);
-                    if (mInProgressEvents.isEmpty()) {
-                        mInProgressEvents = null;
-
-                        // TODO ntmyren: Also callback for single attribution tag activity changes
-                        if (triggerCallbackIfNeeded && !parent.isRunning()) {
-                            scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                                    parent.packageName, tag, false, event.getAttributionFlags(),
-                                    event.getAttributionChainId());
-                        }
-                    }
-                }
-            }
-        }
-
-        // Finish or pause (no-op) an already paused op
-        private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
-            if (!isPaused()) {
-                Slog.wtf(TAG, "No ops running or paused");
-                return;
-            }
-
-            int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
-            if (indexOfToken < 0) {
-                Slog.wtf(TAG, "No op running or paused for the client");
-                return;
-            } else if (isPausing) {
-                // already paused
-                return;
-            }
-
-            // no need to record a paused event finishing.
-            InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
-            event.numUnfinishedStarts--;
-            if (event.numUnfinishedStarts == 0) {
-                mPausedInProgressEvents.removeAt(indexOfToken);
-                mInProgressStartOpEventPool.release(event);
-                if (mPausedInProgressEvents.isEmpty()) {
-                    mPausedInProgressEvents = null;
-                }
-            }
-        }
-
-        /**
-         * Create an event that will be started, if the op is unpaused.
-         */
-        public void createPaused(@NonNull IBinder clientId, int proxyUid,
-                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
-                int attributionFlags, int attributionChainId) throws RemoteException {
-            startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState, flags, true, false, attributionFlags, attributionChainId);
-        }
-
-        /**
-         * Pause all currently started ops. This will create a HistoricalRegistry
-         */
-        public void pause() {
-            if (!isRunning()) {
-                return;
-            }
-
-            if (mPausedInProgressEvents == null) {
-                mPausedInProgressEvents = new ArrayMap<>(1);
-            }
-
-            for (int i = 0; i < mInProgressEvents.size(); i++) {
-                InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
-                mPausedInProgressEvents.put(event.mClientId, event);
-                finishOrPause(event.mClientId, true, true);
-
-                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                        parent.packageName, tag, false,
-                        event.getAttributionFlags(), event.getAttributionChainId());
-            }
-            mInProgressEvents = null;
-        }
-
-        /**
-         * Unpause all currently paused ops. This will reinitialize their start and duration
-         * times, but keep all other values the same
-         */
-        public void resume() {
-            if (!isPaused()) {
-                return;
-            }
-
-            if (mInProgressEvents == null) {
-                mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
-            }
-            boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
-                    && mInProgressEvents.isEmpty();
-
-            long startTime = System.currentTimeMillis();
-            for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
-                InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
-                mInProgressEvents.put(event.mClientId, event);
-                event.mStartElapsedTime = SystemClock.elapsedRealtime();
-                event.mStartTime = startTime;
-                mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                        parent.packageName, tag, event.mUidState, event.mFlags, startTime,
-                        event.getAttributionFlags(), event.getAttributionChainId());
-                if (shouldSendActive) {
-                    scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
-                            tag, true, event.getAttributionFlags(), event.getAttributionChainId());
-                }
-                // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
-                // TODO ntmyren: figure out how to get the real mode.
-                scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName,
-                        tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
-                        event.getAttributionFlags(), event.getAttributionChainId());
-            }
-            mPausedInProgressEvents = null;
-        }
-
-        /**
-         * Called in the case the client dies without calling finish first
-         *
-         * @param clientId The client that died
-         */
-        void onClientDeath(@NonNull IBinder clientId) {
-            synchronized (AppOpsService.this) {
-                if (!isPaused() && !isRunning()) {
-                    return;
-                }
-
-                ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused()
-                        ? mPausedInProgressEvents : mInProgressEvents;
-                InProgressStartOpEvent deadEvent = events.get(clientId);
-                if (deadEvent != null) {
-                    deadEvent.numUnfinishedStarts = 1;
-                }
-
-                finished(clientId);
-            }
-        }
-
-        /**
-         * Notify that the state of the uid changed
-         *
-         * @param newState The new state
-         */
-        public void onUidStateChanged(@AppOpsManager.UidState int newState) {
-            if (!isPaused() && !isRunning()) {
-                return;
-            }
-
-            boolean isRunning = isRunning();
-            ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events =
-                    isRunning ? mInProgressEvents : mPausedInProgressEvents;
-
-            int numInProgressEvents = events.size();
-            List<IBinder> binders = new ArrayList<>(events.keySet());
-            for (int i = 0; i < numInProgressEvents; i++) {
-                InProgressStartOpEvent event = events.get(binders.get(i));
-
-                if (event != null && event.getUidState() != newState) {
-                    try {
-                        // Remove all but one unfinished start count and then call finished() to
-                        // remove start event object
-                        int numPreviousUnfinishedStarts = event.numUnfinishedStarts;
-                        event.numUnfinishedStarts = 1;
-                        OpEventProxyInfo proxy = event.getProxy();
-
-                        finished(event.getClientId(), false);
-
-                        // Call started() to add a new start event object and then add the
-                        // previously removed unfinished start counts back
-                        if (proxy != null) {
-                            startedOrPaused(event.getClientId(), proxy.getUid(),
-                                    proxy.getPackageName(), proxy.getAttributionTag(), newState,
-                                    event.getFlags(), false, isRunning,
-                                    event.getAttributionFlags(), event.getAttributionChainId());
-                        } else {
-                            startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
-                                    newState, event.getFlags(), false, isRunning,
-                                    event.getAttributionFlags(), event.getAttributionChainId());
-                        }
-
-                        events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
-                        InProgressStartOpEvent newEvent = events.get(binders.get(i));
-                        if (newEvent != null) {
-                            newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1;
-                        }
-                    } catch (RemoteException e) {
-                        if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState);
-                    }
-                }
-            }
-        }
-
-        /**
-         * Combine {@code a} and {@code b} and return the result. The result might be {@code a}
-         * or {@code b}. If there is an event for the same key in both the later event is retained.
-         */
-        private @Nullable LongSparseArray<NoteOpEvent> add(@Nullable LongSparseArray<NoteOpEvent> a,
-                @Nullable LongSparseArray<NoteOpEvent> b) {
-            if (a == null) {
-                return b;
-            }
-
-            if (b == null) {
-                return a;
-            }
-
-            int numEventsToAdd = b.size();
-            for (int i = 0; i < numEventsToAdd; i++) {
-                long keyOfEventToAdd = b.keyAt(i);
-                NoteOpEvent bEvent = b.valueAt(i);
-                NoteOpEvent aEvent = a.get(keyOfEventToAdd);
-
-                if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
-                    a.put(keyOfEventToAdd, bEvent);
-                }
-            }
-
-            return a;
-        }
-
-        /**
-         * Add all data from the {@code opToAdd} to this op.
-         *
-         * <p>If there is an event for the same key in both the later event is retained.
-         * <p>{@code opToAdd} should not be used after this method is called.
-         *
-         * @param opToAdd The op to add
-         */
-        public void add(@NonNull AttributedOp opToAdd) {
-            if (opToAdd.isRunning() || opToAdd.isPaused()) {
-                ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = opToAdd.isRunning()
-                        ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
-                Slog.w(TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
-                        + opToAdd.isRunning());
-
-                int numInProgressEvents = ignoredEvents.size();
-                for (int i = 0; i < numInProgressEvents; i++) {
-                    InProgressStartOpEvent event = ignoredEvents.valueAt(i);
-
-                    event.finish();
-                    mInProgressStartOpEventPool.release(event);
-                }
-            }
-
-            mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
-            mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
-        }
-
-        public boolean isRunning() {
-            return mInProgressEvents != null && !mInProgressEvents.isEmpty();
-        }
-
-        public boolean isPaused() {
-            return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
-        }
-
-        boolean hasAnyTime() {
-            return (mAccessEvents != null && mAccessEvents.size() > 0)
-                    || (mRejectEvents != null && mRejectEvents.size() > 0);
-        }
-
-        /**
-         * Clone a {@link LongSparseArray} and clone all values.
-         */
-        private @Nullable LongSparseArray<NoteOpEvent> deepClone(
-                @Nullable LongSparseArray<NoteOpEvent> original) {
-            if (original == null) {
-                return original;
-            }
-
-            int size = original.size();
-            LongSparseArray<NoteOpEvent> clone = new LongSparseArray<>(size);
-            for (int i = 0; i < size; i++) {
-                clone.put(original.keyAt(i), new NoteOpEvent(original.valueAt(i)));
-            }
-
-            return clone;
-        }
-
-        @NonNull AttributedOpEntry createAttributedOpEntryLocked() {
-            LongSparseArray<NoteOpEvent> accessEvents = deepClone(mAccessEvents);
-
-            // Add in progress events as access events
-            if (isRunning()) {
-                long now = SystemClock.elapsedRealtime();
-                int numInProgressEvents = mInProgressEvents.size();
-
-                if (accessEvents == null) {
-                    accessEvents = new LongSparseArray<>(numInProgressEvents);
-                }
-
-                for (int i = 0; i < numInProgressEvents; i++) {
-                    InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
-
-                    accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
-                            new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(),
-                                    event.getProxy()));
-                }
-            }
-
-            LongSparseArray<NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
-
-            return new AttributedOpEntry(parent.op, isRunning(), accessEvents, rejectEvents);
-        }
-    }
-
     final class Op {
         int op;
         int uid;
@@ -1425,7 +645,7 @@
 
             attributedOp = mAttributions.get(attributionTag);
             if (attributedOp == null) {
-                attributedOp = new AttributedOp(attributionTag, parent);
+                attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
                 mAttributions.put(attributionTag, attributedOp);
             }
 
@@ -1675,7 +895,7 @@
     /**
      * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
      */
-    private static void onClientDeath(@NonNull AttributedOp attributedOp,
+    static void onClientDeath(@NonNull AttributedOp attributedOp,
             @NonNull IBinder clientId) {
         attributedOp.onClientDeath(clientId);
     }
@@ -1714,6 +934,8 @@
         }
         mAppOpsServiceInterface =
                 new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                mAppOpsServiceInterface);
 
         LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
         mFile = new AtomicFile(storagePath, "appops");
@@ -1916,7 +1138,7 @@
                 PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
                         PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
                 if (isSamplingTarget(pi)) {
-                    synchronized (this) {
+                    synchronized (AppOpsService.this) {
                         mRarelyUsedPackages.add(packageName);
                     }
                 }
@@ -4016,7 +3238,7 @@
         }
     }
 
-    private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
+    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
             String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
             int attributionFlags, int attributionChainId) {
         ArraySet<ActiveCallback> dispatchedCallbacks = null;
@@ -4069,7 +3291,7 @@
         }
     }
 
-    private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
             String attributionTag, @OpFlags int flags, @Mode int result,
             @AppOpsManager.OnOpStartedListener.StartedType int startedType,
             @AttributionFlags int attributionFlags, int attributionChainId) {
@@ -5595,6 +4817,8 @@
         pw.println("    Only output the watcher sections.");
         pw.println("  --history");
         pw.println("    Only output history.");
+        pw.println("  --uid-state-changes");
+        pw.println("    Include logs about uid state changes.");
     }
 
     private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@@ -5700,10 +4924,11 @@
             long maxNumStarts = 0;
             int numInProgressEvents = attributedOp.mInProgressEvents.size();
             for (int i = 0; i < numInProgressEvents; i++) {
-                InProgressStartOpEvent event = attributedOp.mInProgressEvents.valueAt(i);
+                AttributedOp.InProgressStartOpEvent event =
+                        attributedOp.mInProgressEvents.valueAt(i);
 
                 earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
-                maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts);
+                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
             }
 
             pw.print(prefix + "Running start at: ");
@@ -5731,6 +4956,7 @@
         // TODO ntmyren: Remove the dumpHistory and dumpFilter
         boolean dumpHistory = false;
         boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
         int nDiscreteOps = 10;
         @HistoricalOpsRequestFilter int dumpFilter = 0;
         boolean dumpAll = false;
@@ -5813,6 +5039,8 @@
                 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
                     pw.println("Unknown option: " + arg);
                     return;
+                } else if ("--uid-state-changes".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
                 } else {
                     pw.println("Unknown command: " + arg);
                     return;
@@ -6135,124 +5363,8 @@
                 pw.println();
             }
 
-            final int globalRestrictionCount = mOpGlobalRestrictions.size();
-            for (int i = 0; i < globalRestrictionCount; i++) {
-                IBinder token = mOpGlobalRestrictions.keyAt(i);
-                ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
-                ArraySet<Integer> restrictedOps = restrictionState.mRestrictedOps;
-
-                pw.println("  Global restrictions for token " + token + ":");
-                StringBuilder restrictedOpsValue = new StringBuilder();
-                restrictedOpsValue.append("[");
-                final int restrictedOpCount = restrictedOps.size();
-                for (int j = 0; j < restrictedOpCount; j++) {
-                    if (restrictedOpsValue.length() > 1) {
-                        restrictedOpsValue.append(", ");
-                    }
-                    restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.valueAt(j)));
-                }
-                restrictedOpsValue.append("]");
-                pw.println("      Restricted ops: " + restrictedOpsValue);
-
-            }
-
-            final int userRestrictionCount = mOpUserRestrictions.size();
-            for (int i = 0; i < userRestrictionCount; i++) {
-                IBinder token = mOpUserRestrictions.keyAt(i);
-                ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-                boolean printedTokenHeader = false;
-
-                if (dumpMode >= 0 || dumpWatchers || dumpHistory) {
-                    continue;
-                }
-
-                final int restrictionCount = restrictionState.perUserRestrictions != null
-                        ? restrictionState.perUserRestrictions.size() : 0;
-                if (restrictionCount > 0 && dumpPackage == null) {
-                    boolean printedOpsHeader = false;
-                    for (int j = 0; j < restrictionCount; j++) {
-                        int userId = restrictionState.perUserRestrictions.keyAt(j);
-                        boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
-                        if (restrictedOps == null) {
-                            continue;
-                        }
-                        if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
-                                || !restrictedOps[dumpOp])) {
-                            continue;
-                        }
-                        if (!printedTokenHeader) {
-                            pw.println("  User restrictions for token " + token + ":");
-                            printedTokenHeader = true;
-                        }
-                        if (!printedOpsHeader) {
-                            pw.println("      Restricted ops:");
-                            printedOpsHeader = true;
-                        }
-                        StringBuilder restrictedOpsValue = new StringBuilder();
-                        restrictedOpsValue.append("[");
-                        final int restrictedOpCount = restrictedOps.length;
-                        for (int k = 0; k < restrictedOpCount; k++) {
-                            if (restrictedOps[k]) {
-                                if (restrictedOpsValue.length() > 1) {
-                                    restrictedOpsValue.append(", ");
-                                }
-                                restrictedOpsValue.append(AppOpsManager.opToName(k));
-                            }
-                        }
-                        restrictedOpsValue.append("]");
-                        pw.print("        "); pw.print("user: "); pw.print(userId);
-                                pw.print(" restricted ops: "); pw.println(restrictedOpsValue);
-                    }
-                }
-
-                final int excludedPackageCount = restrictionState.perUserExcludedPackageTags != null
-                        ? restrictionState.perUserExcludedPackageTags.size() : 0;
-                if (excludedPackageCount > 0 && dumpOp < 0) {
-                    IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-                    ipw.increaseIndent();
-                    boolean printedPackagesHeader = false;
-                    for (int j = 0; j < excludedPackageCount; j++) {
-                        int userId = restrictionState.perUserExcludedPackageTags.keyAt(j);
-                        PackageTagsList packageNames =
-                                restrictionState.perUserExcludedPackageTags.valueAt(j);
-                        if (packageNames == null) {
-                            continue;
-                        }
-                        boolean hasPackage;
-                        if (dumpPackage != null) {
-                            hasPackage = packageNames.includes(dumpPackage);
-                        } else {
-                            hasPackage = true;
-                        }
-                        if (!hasPackage) {
-                            continue;
-                        }
-                        if (!printedTokenHeader) {
-                            ipw.println("User restrictions for token " + token + ":");
-                            printedTokenHeader = true;
-                        }
-
-                        ipw.increaseIndent();
-                        if (!printedPackagesHeader) {
-                            ipw.println("Excluded packages:");
-                            printedPackagesHeader = true;
-                        }
-
-                        ipw.increaseIndent();
-                        ipw.print("user: ");
-                        ipw.print(userId);
-                        ipw.println(" packages: ");
-
-                        ipw.increaseIndent();
-                        packageNames.dump(ipw);
-
-                        ipw.decreaseIndent();
-                        ipw.decreaseIndent();
-                        ipw.decreaseIndent();
-                    }
-                    ipw.decreaseIndent();
-                }
-            }
+            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
 
             if (!dumpHistory && !dumpWatchers) {
                 pw.println();
@@ -6264,6 +5376,12 @@
                     pw.println("  AppOps policy not set.");
                 }
             }
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
         }
 
         // Must not hold the appops lock
@@ -6276,14 +5394,6 @@
             mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
                     dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
         }
-
-        if (dumpAll) {
-            pw.println();
-            pw.println("Uid State Changes Event Log:");
-            if (mUidStateTracker != null) {
-                mUidStateTracker.dumpEvents(pw);
-            }
-        }
     }
 
     @Override
@@ -6872,8 +5982,6 @@
 
     private final class ClientUserRestrictionState implements DeathRecipient {
         private final IBinder token;
-        SparseArray<boolean[]> perUserRestrictions;
-        SparseArray<PackageTagsList> perUserExcludedPackageTags;
 
         ClientUserRestrictionState(IBinder token)
                 throws RemoteException {
@@ -6883,134 +5991,29 @@
 
         public boolean setRestriction(int code, boolean restricted,
                 PackageTagsList excludedPackageTags, int userId) {
-            boolean changed = false;
-
-            if (perUserRestrictions == null && restricted) {
-                perUserRestrictions = new SparseArray<>();
-            }
-
-            int[] users;
-            if (userId == UserHandle.USER_ALL) {
-                // TODO(b/162888972): this call is returning all users, not just live ones - we
-                // need to either fix the method called, or rename the variable
-                List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
-
-                users = new int[liveUsers.size()];
-                for (int i = 0; i < liveUsers.size(); i++) {
-                    users[i] = liveUsers.get(i).id;
-                }
-            } else {
-                users = new int[]{userId};
-            }
-
-            if (perUserRestrictions != null) {
-                int numUsers = users.length;
-
-                for (int i = 0; i < numUsers; i++) {
-                    int thisUserId = users[i];
-
-                    boolean[] userRestrictions = perUserRestrictions.get(thisUserId);
-                    if (userRestrictions == null && restricted) {
-                        userRestrictions = new boolean[AppOpsManager._NUM_OP];
-                        perUserRestrictions.put(thisUserId, userRestrictions);
-                    }
-                    if (userRestrictions != null && userRestrictions[code] != restricted) {
-                        userRestrictions[code] = restricted;
-                        if (!restricted && isDefault(userRestrictions)) {
-                            perUserRestrictions.remove(thisUserId);
-                            userRestrictions = null;
-                        }
-                        changed = true;
-                    }
-
-                    if (userRestrictions != null) {
-                        final boolean noExcludedPackages =
-                                excludedPackageTags == null || excludedPackageTags.isEmpty();
-                        if (perUserExcludedPackageTags == null && !noExcludedPackages) {
-                            perUserExcludedPackageTags = new SparseArray<>();
-                        }
-                        if (perUserExcludedPackageTags != null) {
-                            if (noExcludedPackages) {
-                                perUserExcludedPackageTags.remove(thisUserId);
-                                if (perUserExcludedPackageTags.size() <= 0) {
-                                    perUserExcludedPackageTags = null;
-                                }
-                            } else {
-                                perUserExcludedPackageTags.put(thisUserId, excludedPackageTags);
-                            }
-                            changed = true;
-                        }
-                    }
-                }
-            }
-
-            return changed;
+            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
+                    restricted, excludedPackageTags);
         }
 
-        public boolean hasRestriction(int restriction, String packageName, String attributionTag,
+        public boolean hasRestriction(int code, String packageName, String attributionTag,
                 int userId, boolean isCheckOp) {
-            if (perUserRestrictions == null) {
-                return false;
-            }
-            boolean[] restrictions = perUserRestrictions.get(userId);
-            if (restrictions == null) {
-                return false;
-            }
-            if (!restrictions[restriction]) {
-                return false;
-            }
-            if (perUserExcludedPackageTags == null) {
-                return true;
-            }
-            PackageTagsList perUserExclusions = perUserExcludedPackageTags.get(userId);
-            if (perUserExclusions == null) {
-                return true;
-            }
-
-            // TODO (b/240617242) add overload for checkOp to support attribution tags
-            if (isCheckOp) {
-                return !perUserExclusions.includes(packageName);
-            }
-            return !perUserExclusions.contains(packageName, attributionTag);
+            return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
+                    attributionTag, isCheckOp);
         }
 
         public void removeUser(int userId) {
-            if (perUserExcludedPackageTags != null) {
-                perUserExcludedPackageTags.remove(userId);
-                if (perUserExcludedPackageTags.size() <= 0) {
-                    perUserExcludedPackageTags = null;
-                }
-            }
-            if (perUserRestrictions != null) {
-                perUserRestrictions.remove(userId);
-                if (perUserRestrictions.size() <= 0) {
-                    perUserRestrictions = null;
-                }
-            }
+            mAppOpsRestrictions.clearUserRestrictions(token, userId);
         }
 
         public boolean isDefault() {
-            return perUserRestrictions == null || perUserRestrictions.size() <= 0;
+            return !mAppOpsRestrictions.hasUserRestrictions(token);
         }
 
         @Override
         public void binderDied() {
             synchronized (AppOpsService.this) {
+                mAppOpsRestrictions.clearUserRestrictions(token);
                 mOpUserRestrictions.remove(token);
-                if (perUserRestrictions == null) {
-                    return;
-                }
-                final int userCount = perUserRestrictions.size();
-                for (int i = 0; i < userCount; i++) {
-                    final boolean[] restrictions = perUserRestrictions.valueAt(i);
-                    final int restrictionCount = restrictions.length;
-                    for (int j = 0; j < restrictionCount; j++) {
-                        if (restrictions[j]) {
-                            final int changedCode = j;
-                            mHandler.post(() -> notifyWatchersOfChange(changedCode, UID_ANY));
-                        }
-                    }
-                }
                 destroy();
             }
         }
@@ -7018,23 +6021,10 @@
         public void destroy() {
             token.unlinkToDeath(this, 0);
         }
-
-        private boolean isDefault(boolean[] array) {
-            if (ArrayUtils.isEmpty(array)) {
-                return true;
-            }
-            for (boolean value : array) {
-                if (value) {
-                    return false;
-                }
-            }
-            return true;
-        }
     }
 
     private final class ClientGlobalRestrictionState implements DeathRecipient {
         final IBinder mToken;
-        final ArraySet<Integer> mRestrictedOps = new ArraySet<>();
 
         ClientGlobalRestrictionState(IBinder token)
                 throws RemoteException {
@@ -7043,23 +6033,21 @@
         }
 
         boolean setRestriction(int code, boolean restricted) {
-            if (restricted) {
-                return mRestrictedOps.add(code);
-            } else {
-                return mRestrictedOps.remove(code);
-            }
+            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
         }
 
         boolean hasRestriction(int code) {
-            return mRestrictedOps.contains(code);
+            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
         }
 
         boolean isDefault() {
-            return mRestrictedOps.isEmpty();
+            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
         }
 
         @Override
         public void binderDied() {
+            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+            mOpGlobalRestrictions.remove(mToken);
             destroy();
         }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index c4a9a4b..18f659e 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -148,6 +148,14 @@
     /**
      * 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);
+
+    /**
+     * 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
@@ -198,5 +206,4 @@
      * @param printWriter writer to dump to.
      */
     boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
-
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 3da121b..742bf4b 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -30,10 +30,11 @@
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
 
-import android.os.Handler;
+import android.annotation.CallbackExecutor;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 interface AppOpsUidStateTracker {
 
@@ -95,7 +96,7 @@
     /**
      * Listen to changes in {@link android.app.AppOpsManager.UidState}
      */
-    void addUidStateChangedCallback(Handler handler,
+    void addUidStateChangedCallback(@CallbackExecutor Executor executor,
             UidStateChangedCallback callback);
 
     /**
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ca5bfb3..3c281d1 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -22,11 +22,11 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.ProcessCapability;
+import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
 import static android.app.AppOpsManager.UID_STATE_TOP;
@@ -44,17 +44,18 @@
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.Clock;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.PrintWriter;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
 
     private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName();
 
-    private final Handler mHandler;
+    private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
     private AppOpsService.Constants mConstants;
@@ -68,18 +69,46 @@
     private SparseLongArray mPendingCommitTime = new SparseLongArray();
     private SparseBooleanArray mPendingGone = new SparseBooleanArray();
 
-    private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
+    private ArrayMap<UidStateChangedCallback, Executor>
+            mUidStateChangedCallbacks = new ArrayMap<>();
 
     private final EventLog mEventLog;
 
+    @VisibleForTesting
+    interface DelayableExecutor extends Executor {
+
+        void execute(Runnable runnable);
+
+        void executeDelayed(Runnable runnable, long delay);
+    }
+
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            Handler handler, Clock clock, AppOpsService.Constants constants) {
+            Handler handler, Executor lockingExecutor, Clock clock,
+            AppOpsService.Constants constants) {
+
+        this(activityManagerInternal, new DelayableExecutor() {
+            @Override
+            public void execute(Runnable runnable) {
+                handler.post(() -> lockingExecutor.execute(runnable));
+            }
+
+            @Override
+            public void executeDelayed(Runnable runnable, long delay) {
+                handler.postDelayed(() -> lockingExecutor.execute(runnable), delay);
+            }
+        }, clock, constants, handler.getLooper().getThread());
+    }
+
+    @VisibleForTesting
+    AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
+            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
-        mHandler = handler;
+        mExecutor = executor;
         mClock = clock;
         mConstants = constants;
 
-        mEventLog = new EventLog(handler);
+        mEventLog = new EventLog(executor, executorThread);
     }
 
     @Override
@@ -89,7 +118,7 @@
 
     private int getUidStateLocked(int uid) {
         updateUidPendingStateIfNeeded(uid);
-        return mUidStates.get(uid, UID_STATE_CACHED);
+        return mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
     }
 
     @Override
@@ -157,11 +186,12 @@
     }
 
     @Override
-    public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) {
+    public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) {
         if (mUidStateChangedCallbacks.containsKey(callback)) {
             throw new IllegalStateException("Callback is already registered.");
         }
-        mUidStateChangedCallbacks.put(callback, handler);
+
+        mUidStateChangedCallbacks.put(callback, executor);
     }
 
     @Override
@@ -191,8 +221,13 @@
 
         int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE);
         int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+        int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+        int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
         long pendingStateCommitTime = mPendingCommitTime.get(uid, 0);
-        if (uidState != prevUidState || capability != prevCapability) {
+        if ((pendingStateCommitTime == 0
+                && (uidState != prevUidState || capability != prevCapability))
+                || (pendingStateCommitTime != 0
+                && (uidState != pendingUidState || capability != pendingCapability))) {
             mPendingUidStates.put(uid, uidState);
             mPendingCapability.put(uid, capability);
 
@@ -227,7 +262,7 @@
                 final long commitTime = mClock.elapsedRealtime() + settleTime;
                 mPendingCommitTime.put(uid, commitTime);
 
-                mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+                mExecutor.executeDelayed(PooledLambda.obtainRunnable(
                                 AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this,
                                 uid), settleTime + 1);
             }
@@ -236,7 +271,7 @@
 
     @Override
     public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) {
-        int state = mUidStates.get(uid, UID_STATE_CACHED);
+        int state = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
         // if no pendingState set to state to suppress output
         int pendingState = mPendingUidStates.get(uid, state);
         pw.print("    state=");
@@ -294,11 +329,11 @@
     }
 
     private void commitUidPendingState(int uid) {
-        int pendingUidState = mPendingUidStates.get(uid, UID_STATE_CACHED);
+        int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
         int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
         boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false);
 
-        int uidState = mUidStates.get(uid, UID_STATE_CACHED);
+        int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
         int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
         boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
 
@@ -318,10 +353,11 @@
 
             for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
                 UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
-                Handler h = mUidStateChangedCallbacks.valueAt(i);
+                Executor executor = mUidStateChangedCallbacks.valueAt(i);
 
-                h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged,
-                        cb, uid, pendingUidState, foregroundChange));
+                executor.execute(PooledLambda.obtainRunnable(
+                        UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState,
+                        foregroundChange));
             }
         }
 
@@ -361,7 +397,8 @@
         // Memory usage: 24 * size bytes
         private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
 
-        private final Handler mHandler;
+        private final DelayableExecutor mExecutor;
+        private final Thread mExecutorThread;
 
         private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3];
         private long[] mUpdateUidProcStateLogTimestamps =
@@ -379,15 +416,16 @@
         private int mEvalForegroundModeLogSize = 0;
         private int mEvalForegroundModeLogHead = 0;
 
-        EventLog(Handler handler) {
-            mHandler = handler;
+        EventLog(DelayableExecutor executor, Thread executorThread) {
+            mExecutor = executor;
+            mExecutorThread = executorThread;
         }
 
         void logUpdateUidProcState(int uid, int procState, int capability) {
             if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync,
                     this, System.currentTimeMillis(), uid, procState, capability));
         }
 
@@ -411,7 +449,7 @@
             if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, visible));
         }
 
@@ -437,7 +475,7 @@
             if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, code, result));
         }
 
@@ -461,22 +499,6 @@
         }
 
         void dumpEvents(PrintWriter pw) {
-            if (Thread.currentThread() != mHandler.getLooper().getThread()) {
-                // All operations are done on the handler's thread
-                CountDownLatch latch = new CountDownLatch(1);
-                mHandler.post(() -> {
-                    dumpEvents(pw);
-                    latch.countDown();
-                });
-
-                try {
-                    latch.await();
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return;
-            }
-
             int updateIdx = 0;
             int commitIdx = 0;
             int evalIdx = 0;
@@ -531,13 +553,13 @@
             pw.print(" UPDATE_UID_PROC_STATE");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" procState=");
             pw.print(String.format("%-30s", ActivityManager.procStateToString(procState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.println();
         }
@@ -554,13 +576,13 @@
             pw.print(" COMMIT_UID_STATE     ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" visibleAppWidget=");
             pw.print(visibleAppWidget);
@@ -581,13 +603,13 @@
             pw.print(" EVAL_FOREGROUND_MODE ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" code=");
             pw.print(String.format("%-20s", AppOpsManager.opToName(code)));
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
new file mode 100644
index 0000000..dcc36bc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -0,0 +1,870 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.MODE_ALLOWED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
+import static android.app.AppOpsManager.makeKey;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.Pools;
+import android.util.Slog;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+final class AttributedOp {
+    private final @NonNull AppOpsService mAppOpsService;
+    public final @Nullable String tag;
+    public final @NonNull AppOpsService.Op parent;
+
+    /**
+     * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
+     *
+     * <p>Key is {@link AppOpsManager#makeKey}
+     */
+    // TODO(b/248108338)
+    // @GuardedBy("mAppOpsService")
+    private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
+
+    /**
+     * Last rejected accesses for each uidState/opFlag combination
+     *
+     * <p>Key is {@link AppOpsManager#makeKey}
+     */
+    // TODO(b/248108338)
+    // @GuardedBy("mAppOpsService")
+    private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
+
+    /**
+     * Currently in progress startOp events
+     *
+     * <p>Key is clientId
+     */
+    // TODO(b/248108338)
+    // @GuardedBy("mAppOpsService")
+    @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
+
+    /**
+     * Currently paused startOp events
+     *
+     * <p>Key is clientId
+     */
+    // TODO(b/248108338)
+    // @GuardedBy("mAppOpsService")
+    @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
+
+    AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
+            @NonNull AppOpsService.Op parent) {
+        mAppOpsService = appOpsService;
+        this.tag = tag;
+        this.parent = parent;
+    }
+
+    /**
+     * Update state when noteOp was rejected or startOp->finishOp event finished
+     *
+     * @param proxyUid            The uid of the proxy
+     * @param proxyPackageName    The package name of the proxy
+     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param uidState            UID state of the app noteOp/startOp was called for
+     * @param flags               OpFlags of the call
+     */
+    public void accessed(int proxyUid, @Nullable String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags) {
+        long accessTime = System.currentTimeMillis();
+        accessed(accessTime, -1, proxyUid, proxyPackageName,
+                proxyAttributionTag, uidState, flags);
+
+        mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                parent.packageName, tag, uidState, flags, accessTime,
+                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+    }
+
+    /**
+     * Add an access that was previously collected.
+     *
+     * @param noteTime            The time of the event
+     * @param duration            The duration of the event
+     * @param proxyUid            The uid of the proxy
+     * @param proxyPackageName    The package name of the proxy
+     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param uidState            UID state of the app noteOp/startOp was called for
+     * @param flags               OpFlags of the call
+     */
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    public void accessed(long noteTime, long duration, int proxyUid,
+            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+        long key = makeKey(uidState, flags);
+
+        if (mAccessEvents == null) {
+            mAccessEvents = new LongSparseArray<>(1);
+        }
+
+        AppOpsManager.OpEventProxyInfo proxyInfo = null;
+        if (proxyUid != Process.INVALID_UID) {
+            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+                    proxyAttributionTag);
+        }
+
+        AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
+        if (existingEvent != null) {
+            existingEvent.reinit(noteTime, duration, proxyInfo,
+                    mAppOpsService.mOpEventProxyInfoPool);
+        } else {
+            mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo));
+        }
+    }
+
+    /**
+     * Update state when noteOp/startOp was rejected.
+     *
+     * @param uidState UID state of the app noteOp is called for
+     * @param flags    OpFlags of the call
+     */
+    public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+        rejected(System.currentTimeMillis(), uidState, flags);
+
+        mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
+                parent.packageName, tag, uidState, flags);
+    }
+
+    /**
+     * Add an rejection that was previously collected
+     *
+     * @param noteTime The time of the event
+     * @param uidState UID state of the app noteOp/startOp was called for
+     * @param flags    OpFlags of the call
+     */
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags) {
+        long key = makeKey(uidState, flags);
+
+        if (mRejectEvents == null) {
+            mRejectEvents = new LongSparseArray<>(1);
+        }
+
+        // We do not collect proxy information for rejections yet
+        AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key);
+        if (existingEvent != null) {
+            existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool);
+        } else {
+            mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null));
+        }
+    }
+
+    /**
+     * Update state when start was called
+     *
+     * @param clientId            Id of the startOp caller
+     * @param proxyUid            The UID of the proxy app
+     * @param proxyPackageName    The package name of the proxy app
+     * @param proxyAttributionTag The attribution tag of the proxy app
+     * @param uidState            UID state of the app startOp is called for
+     * @param flags               The proxy flags
+     * @param attributionFlags    The attribution flags associated with this operation.
+     * @param attributionChainId  The if of the attribution chain this operations is a part of.
+     */
+    public void started(@NonNull IBinder clientId, int proxyUid,
+            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            @AppOpsManager.AttributionFlags
+                    int attributionFlags, int attributionChainId) throws RemoteException {
+        started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+                uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags,
+                attributionChainId);
+    }
+
+    private void started(@NonNull IBinder clientId, int proxyUid,
+            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId) throws RemoteException {
+        startedOrPaused(clientId, proxyUid, proxyPackageName,
+                proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
+                /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
+    }
+
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags
+            int attributionFlags, int attributionChainId) throws RemoteException {
+        if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
+            mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                    parent.packageName, tag, true, attributionFlags, attributionChainId);
+        }
+
+        if (isStarted && mInProgressEvents == null) {
+            mInProgressEvents = new ArrayMap<>(1);
+        } else if (!isStarted && mPausedInProgressEvents == null) {
+            mPausedInProgressEvents = new ArrayMap<>(1);
+        }
+        ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
+                ? mInProgressEvents : mPausedInProgressEvents;
+
+        long startTime = System.currentTimeMillis();
+        InProgressStartOpEvent event = events.get(clientId);
+        if (event == null) {
+            event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
+                    SystemClock.elapsedRealtime(), clientId, tag,
+                    PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+                    proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+                    attributionFlags, attributionChainId);
+            events.put(clientId, event);
+        } else {
+            if (uidState != event.getUidState()) {
+                onUidStateChanged(uidState);
+            }
+        }
+
+        event.mNumUnfinishedStarts++;
+
+        if (isStarted) {
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                    parent.packageName, tag, uidState, flags, startTime, attributionFlags,
+                    attributionChainId);
+        }
+    }
+
+    /**
+     * Update state when finishOp was called. Will finish started ops, and delete paused ops.
+     *
+     * @param clientId Id of the finishOp caller
+     */
+    public void finished(@NonNull IBinder clientId) {
+        finished(clientId, true);
+    }
+
+    private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
+        finishOrPause(clientId, triggerCallbackIfNeeded, false);
+    }
+
+    /**
+     * Update state when paused or finished is called. If pausing, it records the op as
+     * stopping in the HistoricalRegistry, but does not delete it.
+     */
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
+            boolean isPausing) {
+        int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
+        if (indexOfToken < 0) {
+            finishPossiblyPaused(clientId, isPausing);
+            return;
+        }
+
+        InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
+        if (!isPausing) {
+            event.mNumUnfinishedStarts--;
+        }
+        // If we are pausing, create a NoteOpEvent, but don't change the InProgress event
+        if (event.mNumUnfinishedStarts == 0 || isPausing) {
+            if (!isPausing) {
+                event.finish();
+                mInProgressEvents.removeAt(indexOfToken);
+            }
+
+            if (mAccessEvents == null) {
+                mAccessEvents = new LongSparseArray<>(1);
+            }
+
+            AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null
+                    ? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null;
+
+            long accessDurationMillis =
+                    SystemClock.elapsedRealtime() - event.getStartElapsedTime();
+            AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent(
+                    event.getStartTime(),
+                    accessDurationMillis, proxyCopy);
+            mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
+                    finishedEvent);
+
+            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
+                    parent.packageName, tag, event.getUidState(),
+                    event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
+                    event.getAttributionFlags(), event.getAttributionChainId());
+
+            if (!isPausing) {
+                mAppOpsService.mInProgressStartOpEventPool.release(event);
+                if (mInProgressEvents.isEmpty()) {
+                    mInProgressEvents = null;
+
+                    // TODO ntmyren: Also callback for single attribution tag activity changes
+                    if (triggerCallbackIfNeeded && !parent.isRunning()) {
+                        mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
+                                parent.uid, parent.packageName, tag, false,
+                                event.getAttributionFlags(), event.getAttributionChainId());
+                    }
+                }
+            }
+        }
+    }
+
+    // Finish or pause (no-op) an already paused op
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
+        if (!isPaused()) {
+            Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+            return;
+        }
+
+        int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
+        if (indexOfToken < 0) {
+            Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+            return;
+        } else if (isPausing) {
+            // already paused
+            return;
+        }
+
+        // no need to record a paused event finishing.
+        InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
+        event.mNumUnfinishedStarts--;
+        if (event.mNumUnfinishedStarts == 0) {
+            mPausedInProgressEvents.removeAt(indexOfToken);
+            mAppOpsService.mInProgressStartOpEventPool.release(event);
+            if (mPausedInProgressEvents.isEmpty()) {
+                mPausedInProgressEvents = null;
+            }
+        }
+    }
+
+    /**
+     * Create an event that will be started, if the op is unpaused.
+     */
+    public void createPaused(@NonNull IBinder clientId, int proxyUid,
+            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            @AppOpsManager.AttributionFlags
+                    int attributionFlags, int attributionChainId) throws RemoteException {
+        startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+                uidState, flags, true, false, attributionFlags, attributionChainId);
+    }
+
+    /**
+     * Pause all currently started ops. This will create a HistoricalRegistry
+     */
+    public void pause() {
+        if (!isRunning()) {
+            return;
+        }
+
+        if (mPausedInProgressEvents == null) {
+            mPausedInProgressEvents = new ArrayMap<>(1);
+        }
+
+        for (int i = 0; i < mInProgressEvents.size(); i++) {
+            InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+            mPausedInProgressEvents.put(event.getClientId(), event);
+            finishOrPause(event.getClientId(), true, true);
+
+            mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                    parent.packageName, tag, false,
+                    event.getAttributionFlags(), event.getAttributionChainId());
+        }
+        mInProgressEvents = null;
+    }
+
+    /**
+     * Unpause all currently paused ops. This will reinitialize their start and duration
+     * times, but keep all other values the same
+     */
+    public void resume() {
+        if (!isPaused()) {
+            return;
+        }
+
+        if (mInProgressEvents == null) {
+            mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
+        }
+        boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
+                && mInProgressEvents.isEmpty();
+
+        long startTime = System.currentTimeMillis();
+        for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
+            InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
+            mInProgressEvents.put(event.getClientId(), event);
+            event.setStartElapsedTime(SystemClock.elapsedRealtime());
+            event.setStartTime(startTime);
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                    parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
+                    event.getAttributionFlags(), event.getAttributionChainId());
+            if (shouldSendActive) {
+                mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                        parent.packageName, tag, true, event.getAttributionFlags(),
+                        event.getAttributionChainId());
+            }
+            // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
+            // TODO ntmyren: figure out how to get the real mode.
+            mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
+                    parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
+                    event.getAttributionFlags(), event.getAttributionChainId());
+        }
+        mPausedInProgressEvents = null;
+    }
+
+    /**
+     * Called in the case the client dies without calling finish first
+     *
+     * @param clientId The client that died
+     */
+    void onClientDeath(@NonNull IBinder clientId) {
+        synchronized (mAppOpsService) {
+            if (!isPaused() && !isRunning()) {
+                return;
+            }
+
+            ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused()
+                    ? mPausedInProgressEvents : mInProgressEvents;
+            InProgressStartOpEvent deadEvent = events.get(clientId);
+            if (deadEvent != null) {
+                deadEvent.mNumUnfinishedStarts = 1;
+            }
+
+            finished(clientId);
+        }
+    }
+
+    /**
+     * Notify that the state of the uid changed
+     *
+     * @param newState The new state
+     */
+    public void onUidStateChanged(@AppOpsManager.UidState int newState) {
+        if (!isPaused() && !isRunning()) {
+            return;
+        }
+
+        boolean isRunning = isRunning();
+        ArrayMap<IBinder, InProgressStartOpEvent> events =
+                isRunning ? mInProgressEvents : mPausedInProgressEvents;
+
+        int numInProgressEvents = events.size();
+        List<IBinder> binders = new ArrayList<>(events.keySet());
+        for (int i = 0; i < numInProgressEvents; i++) {
+            InProgressStartOpEvent event = events.get(binders.get(i));
+
+            if (event != null && event.getUidState() != newState) {
+                try {
+                    // Remove all but one unfinished start count and then call finished() to
+                    // remove start event object
+                    int numPreviousUnfinishedStarts = event.mNumUnfinishedStarts;
+                    event.mNumUnfinishedStarts = 1;
+                    AppOpsManager.OpEventProxyInfo proxy = event.getProxy();
+
+                    finished(event.getClientId(), false);
+
+                    // Call started() to add a new start event object and then add the
+                    // previously removed unfinished start counts back
+                    if (proxy != null) {
+                        startedOrPaused(event.getClientId(), proxy.getUid(),
+                                proxy.getPackageName(), proxy.getAttributionTag(), newState,
+                                event.getFlags(), false, isRunning,
+                                event.getAttributionFlags(), event.getAttributionChainId());
+                    } else {
+                        startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
+                                newState, event.getFlags(), false, isRunning,
+                                event.getAttributionFlags(), event.getAttributionChainId());
+                    }
+
+                    events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
+                    InProgressStartOpEvent newEvent = events.get(binders.get(i));
+                    if (newEvent != null) {
+                        newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
+                    }
+                } catch (RemoteException e) {
+                    if (AppOpsService.DEBUG) {
+                        Slog.e(AppOpsService.TAG,
+                                "Cannot switch to new uidState " + newState);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Combine {@code a} and {@code b} and return the result. The result might be {@code a}
+     * or {@code b}. If there is an event for the same key in both the later event is retained.
+     */
+    private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> add(
+            @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> a,
+            @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b) {
+        if (a == null) {
+            return b;
+        }
+
+        if (b == null) {
+            return a;
+        }
+
+        int numEventsToAdd = b.size();
+        for (int i = 0; i < numEventsToAdd; i++) {
+            long keyOfEventToAdd = b.keyAt(i);
+            AppOpsManager.NoteOpEvent bEvent = b.valueAt(i);
+            AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd);
+
+            if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
+                a.put(keyOfEventToAdd, bEvent);
+            }
+        }
+
+        return a;
+    }
+
+    /**
+     * Add all data from the {@code opToAdd} to this op.
+     *
+     * <p>If there is an event for the same key in both the later event is retained.
+     * <p>{@code opToAdd} should not be used after this method is called.
+     *
+     * @param opToAdd The op to add
+     */
+    @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
+    public void add(@NonNull AttributedOp opToAdd) {
+        if (opToAdd.isRunning() || opToAdd.isPaused()) {
+            ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
+                    opToAdd.isRunning()
+                            ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
+            Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
+                    + opToAdd.isRunning());
+
+            int numInProgressEvents = ignoredEvents.size();
+            for (int i = 0; i < numInProgressEvents; i++) {
+                InProgressStartOpEvent event = ignoredEvents.valueAt(i);
+
+                event.finish();
+                mAppOpsService.mInProgressStartOpEventPool.release(event);
+            }
+        }
+
+        mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
+        mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
+    }
+
+    public boolean isRunning() {
+        return mInProgressEvents != null && !mInProgressEvents.isEmpty();
+    }
+
+    public boolean isPaused() {
+        return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
+    }
+
+    boolean hasAnyTime() {
+        return (mAccessEvents != null && mAccessEvents.size() > 0)
+                || (mRejectEvents != null && mRejectEvents.size() > 0);
+    }
+
+    /**
+     * Clone a {@link LongSparseArray} and clone all values.
+     */
+    private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> deepClone(
+            @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> original) {
+        if (original == null) {
+            return original;
+        }
+
+        int size = original.size();
+        LongSparseArray<AppOpsManager.NoteOpEvent> clone = new LongSparseArray<>(size);
+        for (int i = 0; i < size; i++) {
+            clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i)));
+        }
+
+        return clone;
+    }
+
+    @NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() {
+        LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = deepClone(mAccessEvents);
+
+        // Add in progress events as access events
+        if (isRunning()) {
+            long now = SystemClock.elapsedRealtime();
+            int numInProgressEvents = mInProgressEvents.size();
+
+            if (accessEvents == null) {
+                accessEvents = new LongSparseArray<>(numInProgressEvents);
+            }
+
+            for (int i = 0; i < numInProgressEvents; i++) {
+                InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+
+                accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
+                        new AppOpsManager.NoteOpEvent(event.getStartTime(),
+                                now - event.getStartElapsedTime(),
+                                event.getProxy()));
+            }
+        }
+
+        LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
+
+        return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents,
+                rejectEvents);
+    }
+
+    /** A in progress startOp->finishOp event */
+    static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
+        /** Wall clock time of startOp event (not monotonic) */
+        private long mStartTime;
+
+        /** Elapsed time since boot of startOp event */
+        private long mStartElapsedTime;
+
+        /** Id of the client that started the event */
+        private @NonNull IBinder mClientId;
+
+        /** The attribution tag for this operation */
+        private @Nullable String mAttributionTag;
+
+        /** To call when client dies */
+        private @NonNull Runnable mOnDeath;
+
+        /** uidstate used when calling startOp */
+        private @AppOpsManager.UidState int mUidState;
+
+        /** Proxy information of the startOp event */
+        private @Nullable AppOpsManager.OpEventProxyInfo mProxy;
+
+        /** Proxy flag information */
+        private @AppOpsManager.OpFlags int mFlags;
+
+        /** How many times the op was started but not finished yet */
+        int mNumUnfinishedStarts;
+
+        /** The attribution flags related to this event */
+        private @AppOpsManager.AttributionFlags int mAttributionFlags;
+
+        /** The id of the attribution chain this even is a part of */
+        private int mAttributionChainId;
+
+        /**
+         * Create a new {@link InProgressStartOpEvent}.
+         *
+         * @param startTime          The time {@link #startOperation} was called
+         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
+         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param attributionTag     The attribution tag for the operation.
+         * @param onDeath            The code to execute on client death
+         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param attributionFlags   the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
+         * @param proxy              The proxy information, if {@link #startProxyOperation} was
+         *                           called
+         * @param flags              The trusted/nontrusted/self flags.
+         * @throws RemoteException If the client is dying
+         */
+        InProgressStartOpEvent(long startTime, long startElapsedTime,
+                @NonNull IBinder clientId, @Nullable String attributionTag,
+                @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
+                @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
+                throws RemoteException {
+            mStartTime = startTime;
+            mStartElapsedTime = startElapsedTime;
+            mClientId = clientId;
+            mAttributionTag = attributionTag;
+            mOnDeath = onDeath;
+            mUidState = uidState;
+            mProxy = proxy;
+            mFlags = flags;
+            mAttributionFlags = attributionFlags;
+            mAttributionChainId = attributionChainId;
+
+            clientId.linkToDeath(this, 0);
+        }
+
+        /** Clean up event */
+        public void finish() {
+            try {
+                mClientId.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // Either not linked, or already unlinked. Either way, nothing to do.
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mOnDeath.run();
+        }
+
+        /**
+         * Reinit existing object with new state.
+         *
+         * @param startTime          The time {@link #startOperation} was called
+         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
+         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param attributionTag     The attribution tag for this operation.
+         * @param onDeath            The code to execute on client death
+         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param flags              The flags relating to the proxy
+         * @param proxy              The proxy information, if {@link #startProxyOperation}
+         *                           was called
+         * @param attributionFlags   the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
+         * @param proxyPool          The pool to release
+         *                           previous {@link AppOpsManager.OpEventProxyInfo} to
+         * @throws RemoteException If the client is dying
+         */
+        public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
+                @Nullable String attributionTag, @NonNull Runnable onDeath,
+                @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+                @Nullable AppOpsManager.OpEventProxyInfo proxy,
+                @AppOpsManager.AttributionFlags int attributionFlags,
+                int attributionChainId,
+                @NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool
+        ) throws RemoteException {
+            mStartTime = startTime;
+            mStartElapsedTime = startElapsedTime;
+            mClientId = clientId;
+            mAttributionTag = attributionTag;
+            mOnDeath = onDeath;
+            mUidState = uidState;
+            mFlags = flags;
+
+            if (mProxy != null) {
+                proxyPool.release(mProxy);
+            }
+            mProxy = proxy;
+            mAttributionFlags = attributionFlags;
+            mAttributionChainId = attributionChainId;
+
+            clientId.linkToDeath(this, 0);
+        }
+
+        /** @return Wall clock time of startOp event */
+        public long getStartTime() {
+            return mStartTime;
+        }
+
+        /** @return Elapsed time since boot of startOp event */
+        public long getStartElapsedTime() {
+            return mStartElapsedTime;
+        }
+
+        /** @return Id of the client that started the event */
+        public @NonNull IBinder getClientId() {
+            return mClientId;
+        }
+
+        /** @return uidstate used when calling startOp */
+        public @AppOpsManager.UidState int getUidState() {
+            return mUidState;
+        }
+
+        /** @return proxy tag for the access */
+        public @Nullable AppOpsManager.OpEventProxyInfo getProxy() {
+            return mProxy;
+        }
+
+        /** @return flags used for the access */
+        public @AppOpsManager.OpFlags int getFlags() {
+            return mFlags;
+        }
+
+        /** @return attributoin flags used for the access */
+        public @AppOpsManager.AttributionFlags int getAttributionFlags() {
+            return mAttributionFlags;
+        }
+
+        /** @return attribution chain id for the access */
+        public int getAttributionChainId() {
+            return mAttributionChainId;
+        }
+
+        public void setStartTime(long startTime) {
+            mStartTime = startTime;
+        }
+
+        public void setStartElapsedTime(long startElapsedTime) {
+            mStartElapsedTime = startElapsedTime;
+        }
+    }
+
+    /**
+     * An unsynchronized pool of {@link InProgressStartOpEvent} objects.
+     */
+    static class InProgressStartOpEventPool extends Pools.SimplePool<InProgressStartOpEvent> {
+        private OpEventProxyInfoPool mOpEventProxyInfoPool;
+
+        InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool,
+                int maxUnusedPooledObjects) {
+            super(maxUnusedPooledObjects);
+            this.mOpEventProxyInfoPool = opEventProxyInfoPool;
+        }
+
+        InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
+                @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
+                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+                @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+                @AppOpsManager.AttributionFlags
+                        int attributionFlags, int attributionChainId) throws RemoteException {
+
+            InProgressStartOpEvent recycled = acquire();
+
+            AppOpsManager.OpEventProxyInfo proxyInfo = null;
+            if (proxyUid != Process.INVALID_UID) {
+                proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+                        proxyAttributionTag);
+            }
+
+            if (recycled != null) {
+                recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
+                        uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+                        mOpEventProxyInfoPool);
+                return recycled;
+            }
+
+            return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
+                    onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
+        }
+    }
+
+    /**
+     * An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects.
+     */
+    static class OpEventProxyInfoPool extends Pools.SimplePool<AppOpsManager.OpEventProxyInfo> {
+        OpEventProxyInfoPool(int maxUnusedPooledObjects) {
+            super(maxUnusedPooledObjects);
+        }
+
+        AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+                @Nullable String packageName,
+                @Nullable String attributionTag) {
+            AppOpsManager.OpEventProxyInfo recycled = acquire();
+            if (recycled != null) {
+                recycled.reinit(uid, packageName, attributionTag);
+                return recycled;
+            }
+
+            return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
index 80266972..f6fff35 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
@@ -333,6 +333,18 @@
     }
 
     @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);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e90bfe8..35da73e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -377,7 +377,8 @@
                         makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
                         makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+                                streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
+                                btInfo.mAudioSystemDevice,
                                 "onSetBtActiveDevice");
                     }
                     break;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c8aecaf..82b6fa5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,10 +41,12 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.bluetooth.BluetoothAdapter;
@@ -1190,6 +1192,8 @@
         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_index) * 10;
 
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
@@ -1207,7 +1211,7 @@
         mPlaybackMonitor =
                 new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
                         device -> onMuteAwaitConnectionTimeout(device));
-        mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+        mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
 
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
@@ -1313,6 +1317,7 @@
 
         intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
         intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+        intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
 
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
                 Context.RECEIVER_EXPORTED);
@@ -1932,13 +1937,7 @@
         if (state == AudioService.CONNECTION_STATE_CONNECTED) {
             // DEVICE_OUT_HDMI is now connected
             if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
-                sendMsg(mAudioHandler,
-                        MSG_CHECK_MUSIC_ACTIVE,
-                        SENDMSG_REPLACE,
-                        0,
-                        0,
-                        caller,
-                        MUSIC_ACTIVE_POLL_PERIOD_MS);
+                scheduleMusicActiveCheck();
             }
 
             if (isPlatformTelevision()) {
@@ -3827,8 +3826,9 @@
     }
 
     private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+    private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
 
-    private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
+    private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
             new IPlaybackConfigDispatcher.Stub() {
         @Override
         public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3841,19 +3841,26 @@
 
     private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
         boolean voiceActive = false;
+        boolean mediaActive = false;
         for (AudioPlaybackConfiguration config : configs) {
             final int usage = config.getAudioAttributes().getUsage();
-            if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
-                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
-                    && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+            if (!config.isActive()) {
+                continue;
+            }
+            if (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
                 voiceActive = true;
-                break;
+            }
+            if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+                mediaActive = true;
             }
         }
         if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
             updateHearingAidVolumeOnVoiceActivityUpdate();
         }
-
+        if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
+            scheduleMusicActiveCheck();
+        }
         // Update playback active state for all apps in audio mode stack.
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
@@ -4016,7 +4023,7 @@
         }
     }
 
-    private void setLeAudioVolumeOnModeUpdate(int mode) {
+    private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
         switch (mode) {
             case AudioSystem.MODE_IN_COMMUNICATION:
             case AudioSystem.MODE_IN_CALL:
@@ -4030,8 +4037,6 @@
                 return;
         }
 
-        int streamType = getBluetoothContextualVolumeStream(mode);
-
         // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
         // (See AudioDeviceBroker#createBtDeviceInfo())
         int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
@@ -4042,6 +4047,7 @@
                     + index + " maxIndex=" + maxIndex + " streamType=" + streamType);
         }
         mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+        mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "setLeAudioVolumeOnModeUpdate");
     }
 
     private void setStreamVolume(int streamType, int index, int flags,
@@ -5422,7 +5428,7 @@
 
                 // Forcefully set LE audio volume as a workaround, since the value of 'device'
                 // is not DEVICE_OUT_BLE_* even when BLE is connected.
-                setLeAudioVolumeOnModeUpdate(mode);
+                setLeAudioVolumeOnModeUpdate(mode, streamType, device);
 
                 // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
                 // connections not started by the application changing the mode when pid changes
@@ -6037,30 +6043,52 @@
         return mContentResolver;
     }
 
+    private void scheduleMusicActiveCheck() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            cancelMusicActiveCheck();
+            mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+                REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+                new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime()
+                    + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+        }
+    }
+
+    private void cancelMusicActiveCheck() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            if (mMusicActiveIntent != null) {
+                mAlarmManager.cancel(mMusicActiveIntent);
+                mMusicActiveIntent = null;
+            }
+        }
+    }
     private void onCheckMusicActive(String caller) {
         synchronized (mSafeMediaVolumeStateLock) {
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                 int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-
-                if (mSafeMediaVolumeDevices.contains(device)) {
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
+                if (mSafeMediaVolumeDevices.contains(device)
+                        && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+                    scheduleMusicActiveCheck();
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
-                    if (mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
-                            && (index > safeMediaVolumeIndex(device))) {
+                    if (index > safeMediaVolumeIndex(device)) {
                         // Approximate cumulative active music time
-                        mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+                        long curTimeMs = SystemClock.elapsedRealtime();
+                        if (mLastMusicActiveTimeMs != 0) {
+                            mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+                        }
+                        mLastMusicActiveTimeMs = curTimeMs;
+                        Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
                             setSafeMediaVolumeEnabled(true, caller);
                             mMusicActiveMs = 0;
                         }
                         saveMusicActiveMs();
                     }
+                } else {
+                    cancelMusicActiveCheck();
+                    mLastMusicActiveTimeMs = 0;
                 }
             }
         }
@@ -6129,6 +6157,7 @@
                         } else {
                             // We have existing playback time recorded, already confirmed.
                             mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+                            mLastMusicActiveTimeMs = 0;
                         }
                     }
                 } else {
@@ -8638,13 +8667,7 @@
     @VisibleForTesting
     public void checkMusicActive(int deviceType, String caller) {
         if (mSafeMediaVolumeDevices.contains(deviceType)) {
-            sendMsg(mAudioHandler,
-                    MSG_CHECK_MUSIC_ACTIVE,
-                    SENDMSG_REPLACE,
-                    0,
-                    0,
-                    caller,
-                    MUSIC_ACTIVE_POLL_PERIOD_MS);
+            scheduleMusicActiveCheck();
         }
     }
 
@@ -8769,6 +8792,8 @@
                                 suspendedPackages[i], suspendedUids[i]);
                     }
                 }
+            } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
+                onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
             }
         }
     } // end class AudioServiceBroadcastReceiver
@@ -9714,12 +9739,20 @@
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
     private int mMusicActiveMs;
+    private long mLastMusicActiveTimeMs = 0;
+    private PendingIntent mMusicActiveIntent = null;
+    private AlarmManager mAlarmManager;
+
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;  // 30s after boot completed
     // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
     private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
 
+    private static final String ACTION_CHECK_MUSIC_ACTIVE =
+            AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+    private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
     private int safeMediaVolumeIndex(int device) {
         if (!mSafeMediaVolumeDevices.contains(device)) {
             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -9741,14 +9774,9 @@
                 } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
                     mMusicActiveMs = 1;  // nonzero = confirmed
+                    mLastMusicActiveTimeMs = 0;
                     saveMusicActiveMs();
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
+                    scheduleMusicActiveCheck();
                 }
             }
         }
@@ -9790,7 +9818,9 @@
     public void disableSafeMediaVolume(String callingPackage) {
         enforceVolumeController("disable the safe media volume");
         synchronized (mSafeMediaVolumeStateLock) {
+            final long identity = Binder.clearCallingIdentity();
             setSafeMediaVolumeEnabled(false, callingPackage);
+            Binder.restoreCallingIdentity(identity);
             if (mPendingVolumeCommand != null) {
                 onSetStreamVolume(mPendingVolumeCommand.mStreamType,
                                   mPendingVolumeCommand.mIndex,
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResult.java b/services/core/java/com/android/server/biometrics/sensors/AuthResult.java
new file mode 100644
index 0000000..c0ebf6b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthResult.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.server.biometrics.sensors;
+
+import android.hardware.biometrics.BiometricManager;
+
+class AuthResult {
+    static final int FAILED = 0;
+    static final int LOCKED_OUT = 1;
+    static final int AUTHENTICATED = 2;
+    private final int mStatus;
+    private final int mBiometricStrength;
+
+    AuthResult(int status, @BiometricManager.Authenticators.Types int strength) {
+        mStatus = status;
+        mBiometricStrength = strength;
+    }
+
+    int getStatus() {
+        return mStatus;
+    }
+
+    int getBiometricStrength() {
+        return mBiometricStrength;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
new file mode 100644
index 0000000..6d00c3f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
@@ -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.server.biometrics.sensors;
+
+import android.hardware.biometrics.BiometricManager.Authenticators;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that takes in a series of authentication attempts (successes, failures, lockouts)
+ * across different biometric strengths (convenience, weak, strong) and returns a single AuthResult.
+ *
+ * The AuthResult will be the strongest biometric operation that occurred amongst all reported
+ * operations, and if multiple such operations exist, it will favor a successful authentication.
+ */
+class AuthResultCoordinator {
+
+    private static final String TAG = "AuthResultCoordinator";
+    private final List<AuthResult> mOperations;
+
+    AuthResultCoordinator() {
+        mOperations = new ArrayList<>();
+    }
+
+    /**
+     * Adds auth success for a given strength to the current operation list.
+     */
+    void authenticatedFor(@Authenticators.Types int strength) {
+        mOperations.add(new AuthResult(AuthResult.AUTHENTICATED, strength));
+    }
+
+    /**
+     * Adds auth ended for a given strength to the current operation list.
+     */
+    void authEndedFor(@Authenticators.Types int strength) {
+        mOperations.add(new AuthResult(AuthResult.FAILED, strength));
+    }
+
+    /**
+     * Adds a lock out of a given strength to the current operation list.
+     */
+    void lockedOutFor(@Authenticators.Types int strength) {
+        mOperations.add(new AuthResult(AuthResult.LOCKED_OUT, strength));
+    }
+
+    /**
+     * Obtains an auth result & strength from a current set of biometric operations.
+     */
+    AuthResult getResult() {
+        AuthResult result = new AuthResult(AuthResult.FAILED, Authenticators.BIOMETRIC_CONVENIENCE);
+        return mOperations.stream().filter(
+                (element) -> element.getStatus() != AuthResult.FAILED).reduce(result,
+                ((curr, next) -> {
+                    int strengthCompare = curr.getBiometricStrength() - next.getBiometricStrength();
+                    if (strengthCompare < 0) {
+                        return curr;
+                    } else if (strengthCompare == 0) {
+                        // Equal level of strength, favor authentication.
+                        if (curr.getStatus() == AuthResult.AUTHENTICATED) {
+                            return curr;
+                        } else {
+                            // Either next is Authenticated, or it is not, either way return this
+                            // one.
+                            return next;
+                        }
+                    } else {
+                        // curr is a weaker biometric
+                        return next;
+                    }
+                }));
+    }
+
+    void resetState() {
+        mOperations.clear();
+    }
+}
+
+
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
new file mode 100644
index 0000000..13840ff
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.sensors;
+
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.util.Slog;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Coordinates lockout counter enforcement for all types of biometric strengths across all users.
+ *
+ * This class is not thread-safe. In general, all calls to this class should be made on the same
+ * handler to ensure no collisions.
+ */
+class AuthSessionCoordinator implements AuthSessionListener {
+    private static final String TAG = "AuthSessionCoordinator";
+
+    private final Set<Integer> mAuthOperations;
+
+    private int mUserId;
+    private boolean mIsAuthenticating;
+    private AuthResultCoordinator mAuthResultCoordinator;
+    private MultiBiometricLockoutState mMultiBiometricLockoutState;
+
+    AuthSessionCoordinator() {
+        mAuthOperations = new HashSet<>();
+        mAuthResultCoordinator = new AuthResultCoordinator();
+        mMultiBiometricLockoutState = new MultiBiometricLockoutState();
+    }
+
+    /**
+     * A Call indicating that an auth session has started
+     */
+    void onAuthSessionStarted(int userId) {
+        mAuthOperations.clear();
+        mUserId = userId;
+        mIsAuthenticating = true;
+        mAuthResultCoordinator.resetState();
+    }
+
+    /**
+     * Ends the current auth session and updates the lockout state.
+     *
+     * This can happen two ways.
+     * 1. Manually calling this API
+     * 2. If authStartedFor() was called, and all authentication attempts finish.
+     */
+    void endAuthSession() {
+        if (mIsAuthenticating) {
+            mAuthOperations.clear();
+            AuthResult res =
+                    mAuthResultCoordinator.getResult();
+            if (res.getStatus() == AuthResult.AUTHENTICATED) {
+                mMultiBiometricLockoutState.onUserUnlocked(mUserId, res.getBiometricStrength());
+            } else if (res.getStatus() == AuthResult.LOCKED_OUT) {
+                mMultiBiometricLockoutState.onUserLocked(mUserId, res.getBiometricStrength());
+            }
+            mAuthResultCoordinator.resetState();
+            mIsAuthenticating = false;
+        }
+    }
+
+    /**
+     * @return true if a user can authenticate with a given strength.
+     */
+    boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
+        return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
+    }
+
+    @Override
+    public void authStartedFor(int userId, int sensorId) {
+        if (!mIsAuthenticating) {
+            onAuthSessionStarted(userId);
+        }
+
+        if (mAuthOperations.contains(sensorId)) {
+            Slog.e(TAG, "Error, authStartedFor(" + sensorId + ") without being finished");
+            return;
+        }
+
+        if (mUserId != userId) {
+            Slog.e(TAG, "Error authStartedFor(" + userId + ") Incorrect userId, expected" + mUserId
+                    + ", ignoring...");
+            return;
+        }
+
+        mAuthOperations.add(sensorId);
+    }
+
+    @Override
+    public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
+            int sensorId) {
+        mAuthResultCoordinator.authenticatedFor(biometricStrength);
+        attemptToFinish(userId, sensorId,
+                "authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength
+                        + ", sensorId=" + sensorId + "");
+    }
+
+    @Override
+    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
+            int sensorId) {
+        mAuthResultCoordinator.lockedOutFor(biometricStrength);
+        attemptToFinish(userId, sensorId,
+                "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
+                        + ", sensorId=" + sensorId + "");
+    }
+
+    @Override
+    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
+            int sensorId) {
+        mAuthResultCoordinator.authEndedFor(biometricStrength);
+        attemptToFinish(userId, sensorId,
+                "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
+                        + ", sensorId=" + sensorId);
+    }
+
+    @Override
+    public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) {
+        mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength);
+    }
+
+    private void attemptToFinish(int userId, int sensorId, String description) {
+        boolean didFail = false;
+        if (!mAuthOperations.contains(sensorId)) {
+            Slog.e(TAG, "Error unable to find auth operation : " + description);
+            didFail = true;
+        }
+        if (userId != mUserId) {
+            Slog.e(TAG, "Error mismatched userId, expected=" + mUserId + " for " + description);
+            didFail = true;
+        }
+        if (didFail) {
+            return;
+        }
+        mAuthOperations.remove(sensorId);
+        if (mIsAuthenticating && mAuthOperations.isEmpty()) {
+            endAuthSession();
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
new file mode 100644
index 0000000..8b1f90a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.sensors;
+
+import android.hardware.biometrics.BiometricManager.Authenticators;
+
+/**
+ * An interface that listens to authentication events.
+ */
+interface AuthSessionListener {
+    /**
+     * Indicates an auth operation has started for a given user and sensor.
+     */
+    void authStartedFor(int userId, int sensorId);
+
+    /**
+     * Indicates a successful authentication occurred for a sensor of a given strength.
+     */
+    void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
+
+    /**
+     * Indicates authentication ended for a sensor of a given strength.
+     */
+    void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
+
+    /**
+     * Indicates a lockout occurred for a sensor of a given strength.
+     */
+    void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
+
+    /**
+     * Indicates that a reset lockout has happened for a given strength.
+     */
+    void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
new file mode 100644
index 0000000..49dc817
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.sensors;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is used as a system to store the state of each
+ * {@link Authenticators.Types} status for every user.
+ */
+class MultiBiometricLockoutState {
+
+    private static final String TAG = "MultiBiometricLockoutState";
+    private static final Map<Integer, List<Integer>> PRECEDENCE;
+
+    static {
+        Map<Integer, List<Integer>> precedence = new ArrayMap<>();
+        precedence.put(Authenticators.BIOMETRIC_STRONG,
+                Arrays.asList(BIOMETRIC_STRONG, BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
+        precedence.put(BIOMETRIC_WEAK, Arrays.asList(BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
+        precedence.put(BIOMETRIC_CONVENIENCE, Arrays.asList(BIOMETRIC_CONVENIENCE));
+        PRECEDENCE = Collections.unmodifiableMap(precedence);
+    }
+
+    private final Map<Integer, Map<Integer, Boolean>> mCanUserAuthenticate;
+
+    @VisibleForTesting
+    MultiBiometricLockoutState() {
+        mCanUserAuthenticate = new HashMap<>();
+    }
+
+    private static Map<Integer, Boolean> createLockedOutMap() {
+        Map<Integer, Boolean> lockOutMap = new HashMap<>();
+        lockOutMap.put(BIOMETRIC_STRONG, false);
+        lockOutMap.put(BIOMETRIC_WEAK, false);
+        lockOutMap.put(BIOMETRIC_CONVENIENCE, false);
+        return lockOutMap;
+    }
+
+    private Map<Integer, Boolean> getAuthMapForUser(int userId) {
+        if (!mCanUserAuthenticate.containsKey(userId)) {
+            mCanUserAuthenticate.put(userId, createLockedOutMap());
+        }
+        return mCanUserAuthenticate.get(userId);
+    }
+
+    /**
+     * Indicates a {@link Authenticators} has been locked for userId.
+     *
+     * @param userId   The user.
+     * @param strength The strength of biometric that is requested to be locked.
+     */
+    void onUserLocked(int userId, @Authenticators.Types int strength) {
+        Slog.d(TAG, "onUserLocked(userId=" + userId + ", strength=" + strength + ")");
+        Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
+        for (int strengthToLockout : PRECEDENCE.get(strength)) {
+            canUserAuthState.put(strengthToLockout, false);
+        }
+    }
+
+    /**
+     * Indicates that a user has unlocked a {@link Authenticators}
+     *
+     * @param userId   The user.
+     * @param strength The strength of biometric that is unlocked.
+     */
+    void onUserUnlocked(int userId, @Authenticators.Types int strength) {
+        Slog.d(TAG, "onUserUnlocked(userId=" + userId + ", strength=" + strength + ")");
+        Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
+        for (int strengthToLockout : PRECEDENCE.get(strength)) {
+            canUserAuthState.put(strengthToLockout, true);
+        }
+    }
+
+    /**
+     * Indicates if a user can perform an authentication operation with a given
+     * {@link Authenticators.Types}
+     *
+     * @param userId   The user.
+     * @param strength The strength of biometric that is requested to authenticate.
+     * @return If a user can authenticate with a given biometric of this strength.
+     */
+    boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) {
+        final boolean canAuthenticate = getAuthMapForUser(userId).get(strength);
+        Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") ="
+                + canAuthenticate);
+        return canAuthenticate;
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index ab553a8..3ede0a2 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -21,21 +21,23 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.radio.IRadioService;
-import android.util.Slog;
 
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
+
 public class BroadcastRadioService extends SystemService {
-    private static final String TAG = "BcRadioSrv";
     private final IRadioService mServiceImpl;
+
     public BroadcastRadioService(Context context) {
         super(context);
-        mServiceImpl = new BroadcastRadioServiceHidl(this);
+        ArrayList<String> serviceNameList = IRadioServiceAidlImpl.getServicesNames();
+        mServiceImpl = serviceNameList.isEmpty() ? new IRadioServiceHidlImpl(this)
+                : new IRadioServiceAidlImpl(this, serviceNameList);
     }
 
     @Override
     public void onStart() {
-        Slog.v(TAG, "BroadcastRadioService onStart()");
         publishBinderService(Context.RADIO_SERVICE, mServiceImpl.asBinder());
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
new file mode 100644
index 0000000..0770062
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio;
+
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper for AIDL interface for BroadcastRadio HAL
+ */
+final class IRadioServiceAidlImpl extends IRadioService.Stub {
+    private static final String TAG = "BcRadioSrvAidl";
+
+    private static final List<String> SERVICE_NAMES = Arrays.asList(
+            IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab");
+
+    private final com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl mHalAidl;
+    private final BroadcastRadioService mService;
+
+    /**
+     * Gets names of all AIDL BroadcastRadio HAL services available.
+     */
+    public static ArrayList<String> getServicesNames() {
+        ArrayList<String> serviceList = new ArrayList<>();
+        for (int i = 0; i < SERVICE_NAMES.size(); i++) {
+            IBinder serviceBinder = ServiceManager.waitForDeclaredService(SERVICE_NAMES.get(i));
+            if (serviceBinder != null) {
+                serviceList.add(SERVICE_NAMES.get(i));
+            }
+        }
+        return serviceList;
+    }
+
+    IRadioServiceAidlImpl(BroadcastRadioService service, ArrayList<String> serviceList) {
+        Slogf.i(TAG, "Initialize BroadcastRadioServiceAidl(%s)", service);
+        mService = Objects.requireNonNull(service);
+        mHalAidl =
+                new com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl(serviceList);
+    }
+
+    @Override
+    public List<RadioManager.ModuleProperties> listModules() {
+        mService.enforcePolicyAccess();
+        return mHalAidl.listModules();
+    }
+
+    @Override
+    public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
+            boolean withAudio, ITunerCallback callback) throws RemoteException {
+        if (isDebugEnabled()) {
+            Slogf.d(TAG, "Opening module %d", moduleId);
+        }
+        mService.enforcePolicyAccess();
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+    }
+
+    @Override
+    public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+            IAnnouncementListener listener) {
+        if (isDebugEnabled()) {
+            Slogf.d(TAG, "Adding announcement listener for %s", Arrays.toString(enabledTypes));
+        }
+        Objects.requireNonNull(enabledTypes);
+        Objects.requireNonNull(listener);
+        mService.enforcePolicyAccess();
+
+        return mHalAidl.addAnnouncementListener(enabledTypes, listener);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter radioPrintWriter = new IndentingPrintWriter(printWriter);
+        radioPrintWriter.printf("BroadcastRadioService\n");
+
+        radioPrintWriter.increaseIndent();
+        radioPrintWriter.printf("AIDL HAL:\n");
+
+        radioPrintWriter.increaseIndent();
+        mHalAidl.dumpInfo(radioPrintWriter);
+        radioPrintWriter.decreaseIndent();
+
+        radioPrintWriter.decreaseIndent();
+    }
+
+    private static boolean isDebugEnabled() {
+        return Log.isLoggable(TAG, Log.DEBUG);
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
similarity index 96%
rename from services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java
rename to services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 5cb6770..28b6d02 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioServiceHidl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -41,7 +41,7 @@
 /**
  * Wrapper for HIDL interface for BroadcastRadio HAL
  */
-final class BroadcastRadioServiceHidl extends IRadioService.Stub {
+final class IRadioServiceHidlImpl extends IRadioService.Stub {
     private static final String TAG = "BcRadioSrvHidl";
 
     private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1;
@@ -52,7 +52,7 @@
     private final BroadcastRadioService mService;
     private final List<RadioManager.ModuleProperties> mV1Modules;
 
-    BroadcastRadioServiceHidl(BroadcastRadioService service) {
+    IRadioServiceHidlImpl(BroadcastRadioService service) {
         mService = Objects.requireNonNull(service);
         mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);
         mV1Modules = mHal1.loadModules();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
new file mode 100644
index 0000000..b618aa3
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/AnnouncementAggregator.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Announcement aggregator extending {@link ICloseHandle} to support broadcast radio announcement
+ */
+public final class AnnouncementAggregator extends ICloseHandle.Stub {
+    private static final String TAG = "BcRadioAidlSrv.AnnAggr";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Object mLock;
+    private final IAnnouncementListener mListener;
+    private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
+
+    @GuardedBy("mLock")
+    private final List<ModuleWatcher> mModuleWatchers = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
+
+    /**
+     * Constructs Announcement aggregator with AnnouncementListener of BroadcastRadio AIDL HAL.
+     */
+    public AnnouncementAggregator(IAnnouncementListener listener, Object lock) {
+        mListener = Objects.requireNonNull(listener, "listener cannot be null");
+        mLock = Objects.requireNonNull(lock, "lock cannot be null");
+        try {
+            listener.asBinder().linkToDeath(mDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    private final class ModuleWatcher extends IAnnouncementListener.Stub {
+
+        @Nullable
+        private ICloseHandle mCloseHandle;
+
+        public List<Announcement> mCurrentList = new ArrayList<>();
+
+        public void onListUpdated(List<Announcement> active) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onListUpdate for %s", active);
+            }
+            mCurrentList = Objects.requireNonNull(active, "active cannot be null");
+            AnnouncementAggregator.this.onListUpdated();
+        }
+
+        public void setCloseHandle(ICloseHandle closeHandle) {
+            if (DEBUG) {
+                Slogf.d(TAG, "Set close handle %s", closeHandle);
+            }
+            mCloseHandle = Objects.requireNonNull(closeHandle, "closeHandle cannot be null");
+        }
+
+        public void close() throws RemoteException {
+            if (DEBUG) {
+                Slogf.d(TAG, "Close module watcher.");
+            }
+            if (mCloseHandle != null) mCloseHandle.close();
+        }
+
+        public void dumpInfo(IndentingPrintWriter pw) {
+            pw.printf("ModuleWatcher:\n");
+
+            pw.increaseIndent();
+            pw.printf("Close handle: %s\n", mCloseHandle);
+            pw.printf("Current announcement list: %s\n", mCurrentList);
+            pw.decreaseIndent();
+        }
+    }
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        public void binderDied() {
+            try {
+                close();
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "Cannot close Announcement aggregator for DeathRecipient");
+            }
+        }
+    }
+
+    private void onListUpdated() {
+        if (DEBUG) {
+            Slogf.d(TAG, "onListUpdated()");
+        }
+        synchronized (mLock) {
+            if (mIsClosed) {
+                Slogf.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+                return;
+            }
+            List<Announcement> combined = new ArrayList<>(mModuleWatchers.size());
+            for (int i = 0; i < mModuleWatchers.size(); i++) {
+                combined.addAll(mModuleWatchers.get(i).mCurrentList);
+            }
+            try {
+                mListener.onListUpdated(combined);
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "mListener.onListUpdated() failed");
+            }
+        }
+    }
+
+    /**
+     * Watches the given RadioModule by adding Announcement Listener to it
+     */
+    public void watchModule(RadioModule radioModule, int[] enabledTypes) {
+        if (DEBUG) {
+            Slogf.d(TAG, "Watch module for %s with enabled types %s",
+                    radioModule, Arrays.toString(enabledTypes));
+        }
+        synchronized (mLock) {
+            if (mIsClosed) {
+                throw new IllegalStateException("Failed to watch module"
+                        + "since announcement aggregator has already been closed");
+            }
+
+            ModuleWatcher watcher = new ModuleWatcher();
+            ICloseHandle closeHandle;
+            try {
+                closeHandle = radioModule.addAnnouncementListener(watcher, enabledTypes);
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "Failed to add announcement listener");
+                return;
+            }
+            watcher.setCloseHandle(closeHandle);
+            mModuleWatchers.add(watcher);
+        }
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        if (DEBUG) {
+            Slogf.d(TAG, "Close watchModule");
+        }
+        synchronized (mLock) {
+            if (mIsClosed) {
+                Slogf.w(TAG, "Announcement aggregator has already been closed.");
+                return;
+            }
+
+            mListener.asBinder().unlinkToDeath(mDeathRecipient, /* flags= */ 0);
+
+            for (int i = 0; i < mModuleWatchers.size(); i++) {
+                ModuleWatcher moduleWatcher = mModuleWatchers.get(i);
+                try {
+                    moduleWatcher.close();
+                } catch (Exception e) {
+                    Slogf.e(TAG, "Failed to close module watcher %s: %s",
+                            moduleWatcher, e);
+                }
+            }
+            mModuleWatchers.clear();
+
+            mIsClosed = true;
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter announcementPrintWriter = new IndentingPrintWriter(printWriter);
+        announcementPrintWriter.printf("AnnouncementAggregator\n");
+
+        announcementPrintWriter.increaseIndent();
+        synchronized (mLock) {
+            announcementPrintWriter.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
+            announcementPrintWriter.printf("Module Watchers [%d]:\n", mModuleWatchers.size());
+
+            announcementPrintWriter.increaseIndent();
+            for (int i = 0; i < mModuleWatchers.size(); i++) {
+                mModuleWatchers.get(i).dumpInfo(announcementPrintWriter);
+            }
+            announcementPrintWriter.decreaseIndent();
+
+        }
+        announcementPrintWriter.decreaseIndent();
+    }
+
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
new file mode 100644
index 0000000..71ba296
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.IBinder;
+import android.os.IServiceCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Broadcast radio service using BroadcastRadio AIDL HAL
+ */
+public final class BroadcastRadioServiceImpl {
+    private static final String TAG = "BcRadioAidlSrv";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private int mNextModuleId;
+
+    @GuardedBy("mLock")
+    private final Map<String, Integer> mServiceNameToModuleIdMap = new ArrayMap<>();
+
+    // Map from module ID to RadioModule created by mServiceListener.onRegistration().
+    @GuardedBy("mLock")
+    private final SparseArray<RadioModule> mModules = new SparseArray<>();
+
+    private final IServiceCallback.Stub mServiceListener = new IServiceCallback.Stub() {
+        @Override
+        public void onRegistration(String name, final IBinder newBinder) {
+            Slogf.i(TAG, "onRegistration for %s", name);
+            Integer moduleId;
+            synchronized (mLock) {
+                // If the service has been registered before, reuse its previous module ID.
+                moduleId = mServiceNameToModuleIdMap.get(name);
+                boolean newService = false;
+                if (moduleId == null) {
+                    newService = true;
+                    moduleId = mNextModuleId;
+                }
+
+                RadioModule radioModule =
+                        RadioModule.tryLoadingModule(moduleId, name, newBinder, mLock);
+                if (radioModule == null) {
+                    Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
+                    return;
+                }
+                try {
+                    radioModule.setInternalHalCallback();
+                } catch (RemoteException ex) {
+                    Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
+                            + "cannot register HAL callback", name, moduleId);
+                    return;
+                }
+                if (DEBUG) {
+                    Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
+                            name, moduleId);
+                }
+                RadioModule prevModule = mModules.get(moduleId);
+                mModules.put(moduleId, radioModule);
+                if (prevModule != null) {
+                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+                }
+
+                if (newService) {
+                    mServiceNameToModuleIdMap.put(name, moduleId);
+                    mNextModuleId++;
+                }
+
+                try {
+                    BroadcastRadioDeathRecipient deathRecipient =
+                            new BroadcastRadioDeathRecipient(moduleId);
+                    radioModule.getService().asBinder().linkToDeath(deathRecipient, moduleId);
+                } catch (RemoteException ex) {
+                    Slogf.w(TAG, "Service has already died, so remove its entry from mModules.");
+                    mModules.remove(moduleId);
+                }
+            }
+        }
+    };
+
+    private final class BroadcastRadioDeathRecipient implements IBinder.DeathRecipient {
+        private final int mModuleId;
+
+        BroadcastRadioDeathRecipient(int moduleId) {
+            mModuleId = moduleId;
+        }
+
+        @Override
+        public void binderDied() {
+            Slogf.i(TAG, "ServiceDied for module id %d", mModuleId);
+            synchronized (mLock) {
+                RadioModule prevModule = mModules.removeReturnOld(mModuleId);
+                if (prevModule != null) {
+                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+                }
+
+                for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
+                    if (entry.getValue() == mModuleId) {
+                        Slogf.w(TAG, "Service %s died, removed RadioModule with ID %d",
+                                entry.getKey(), mModuleId);
+                        return;
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Constructs BroadcastRadioServiceImpl using AIDL HAL using the list of names of AIDL
+     * BroadcastRadio HAL services {@code serviceNameList}
+     */
+    public BroadcastRadioServiceImpl(ArrayList<String> serviceNameList) {
+        mNextModuleId = 0;
+        if (DEBUG) {
+            Slogf.d(TAG, "Initializing BroadcastRadioServiceImpl %s",
+                    IBroadcastRadio.DESCRIPTOR);
+        }
+        for (int i = 0; i < serviceNameList.size(); i++) {
+            try {
+                ServiceManager.registerForNotifications(serviceNameList.get(i), mServiceListener);
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "failed to register for service notifications for service %s",
+                        serviceNameList.get(i));
+            }
+        }
+    }
+
+    /**
+     * Gets all AIDL {@link com.android.server.broadcastradio.aidl.RadioModule}.
+     */
+    public List<RadioManager.ModuleProperties> listModules() {
+        synchronized (mLock) {
+            List<RadioManager.ModuleProperties> moduleList = new ArrayList<>(mModules.size());
+            for (int i = 0; i < mModules.size(); i++) {
+                moduleList.add(mModules.valueAt(i).mProperties);
+            }
+            return moduleList;
+        }
+    }
+
+    /**
+     * Gets the AIDL RadioModule for the given {@code moduleId}. Null will be returned if not found.
+     */
+    public boolean hasModule(int id) {
+        synchronized (mLock) {
+            return mModules.contains(id);
+        }
+    }
+
+    /**
+     * Returns whether any AIDL {@link com.android.server.broadcastradio.aidl.RadioModule} exists.
+     */
+    public boolean hasAnyModules() {
+        synchronized (mLock) {
+            return mModules.size() != 0;
+        }
+    }
+
+    /**
+     * Opens {@link ITuner} session for the AIDL
+     * {@link com.android.server.broadcastradio.aidl.RadioModule} given {@code moduleId}.
+     */
+    @Nullable
+    public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
+            boolean withAudio, ITunerCallback callback) throws RemoteException {
+        if (DEBUG) {
+            Slogf.d(TAG, "Open AIDL radio session");
+        }
+        Objects.requireNonNull(callback);
+
+        if (!withAudio) {
+            throw new IllegalArgumentException("Non-audio sessions not supported with AIDL HAL");
+        }
+
+        RadioModule radioModule;
+        synchronized (mLock) {
+            radioModule = mModules.get(moduleId);
+            if (radioModule == null) {
+                Slogf.e(TAG, "Invalid module ID %d", moduleId);
+                return null;
+            }
+        }
+
+        TunerSession tunerSession = radioModule.openSession(callback);
+        if (legacyConfig != null) {
+            tunerSession.setConfiguration(legacyConfig);
+        }
+        return tunerSession;
+    }
+
+    /**
+     * Adds AnnouncementListener for every
+     * {@link com.android.server.broadcastradio.aidl.RadioModule}.
+     */
+    public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+            IAnnouncementListener listener) {
+        if (DEBUG) {
+            Slogf.d(TAG, "Add AnnouncementListener with enable types %s",
+                    Arrays.toString(enabledTypes));
+        }
+        AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
+        boolean anySupported = false;
+        synchronized (mLock) {
+            for (int i = 0; i < mModules.size(); i++) {
+                try {
+                    aggregator.watchModule(mModules.valueAt(i), enabledTypes);
+                    anySupported = true;
+                } catch (UnsupportedOperationException ex) {
+                    Slogf.w(TAG, ex, "Announcements not supported for this module");
+                }
+            }
+        }
+        if (!anySupported) {
+            Slogf.w(TAG, "There are no HAL modules that support announcements");
+        }
+        return aggregator;
+    }
+
+    /**
+     * Dump state of broadcastradio service for AIDL HAL.
+     *
+     * @param pw The file to which {@link BroadcastRadioServiceImpl} state is dumped.
+     */
+    public void dumpInfo(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.printf("Next module id available: %d\n", mNextModuleId);
+            pw.printf("ServiceName to module id map:\n");
+
+            pw.increaseIndent();
+            for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
+                pw.printf("Service name: %s, module id: %d\n", entry.getKey(), entry.getValue());
+            }
+            pw.decreaseIndent();
+
+            pw.printf("Radio modules [%d]:\n", mModules.size());
+
+            pw.increaseIndent();
+            for (int i = 0; i < mModules.size(); i++) {
+                pw.printf("Module id=%d:\n", mModules.keyAt(i));
+
+                pw.increaseIndent();
+                mModules.valueAt(i).dumpInfo(pw);
+                pw.decreaseIndent();
+            }
+            pw.decreaseIndent();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
new file mode 100644
index 0000000..d90f9c4
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.Announcement;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.ProgramFilter;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.Result;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.os.ParcelableException;
+import android.os.ServiceSpecificException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A utils class converting data types between AIDL broadcast radio HAL and
+ * {@link android.hardware.radio}
+ */
+final class ConversionUtils {
+    // TODO(b/241118988): Add unit test for ConversionUtils class
+    private static final String TAG = "BcRadioAidlSrv.convert";
+
+    private ConversionUtils() {
+        throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
+    }
+
+    static RuntimeException throwOnError(RuntimeException halException, String action) {
+        if (!(halException instanceof ServiceSpecificException)) {
+            return new ParcelableException(new RuntimeException(
+                    action + ": unknown error"));
+        }
+        int result = ((ServiceSpecificException) halException).errorCode;
+        switch (result) {
+            case Result.UNKNOWN_ERROR:
+                return new ParcelableException(new RuntimeException(action
+                        + ": UNKNOWN_ERROR"));
+            case Result.INTERNAL_ERROR:
+                return new ParcelableException(new RuntimeException(action
+                        + ": INTERNAL_ERROR"));
+            case Result.INVALID_ARGUMENTS:
+                return new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
+            case Result.INVALID_STATE:
+                return new IllegalStateException(action + ": INVALID_STATE");
+            case Result.NOT_SUPPORTED:
+                return new UnsupportedOperationException(action + ": NOT_SUPPORTED");
+            case Result.TIMEOUT:
+                return new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
+            default:
+                return new ParcelableException(new RuntimeException(
+                        action + ": unknown error (" + result + ")"));
+        }
+    }
+
+    static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
+        if (info == null) {
+            return new VendorKeyValue[]{};
+        }
+
+        ArrayList<VendorKeyValue> list = new ArrayList<>();
+        for (Map.Entry<String, String> entry : info.entrySet()) {
+            VendorKeyValue elem = new VendorKeyValue();
+            elem.key = entry.getKey();
+            elem.value = entry.getValue();
+            if (elem.key == null || elem.value == null) {
+                Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
+                        elem.key, elem.value);
+                continue;
+            }
+            list.add(elem);
+        }
+
+        return list.toArray(VendorKeyValue[]::new);
+    }
+
+    static Map<String, String> vendorInfoFromHalVendorKeyValues(@Nullable VendorKeyValue[] info) {
+        if (info == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, String> map = new ArrayMap<>();
+        for (VendorKeyValue kvp : info) {
+            if (kvp.key == null || kvp.value == null) {
+                Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
+                        kvp.key, kvp.value);
+                continue;
+            }
+            map.put(kvp.key, kvp.value);
+        }
+
+        return map;
+    }
+
+    @ProgramSelector.ProgramType
+    private static int identifierTypeToProgramType(
+            @ProgramSelector.IdentifierType int idType) {
+        switch (idType) {
+            case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
+                // TODO(b/69958423): verify AM/FM with frequency range
+                return ProgramSelector.PROGRAM_TYPE_FM;
+            case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
+                // TODO(b/69958423): verify AM/FM with frequency range
+                return ProgramSelector.PROGRAM_TYPE_FM_HD;
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+                return ProgramSelector.PROGRAM_TYPE_DAB;
+            case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
+            case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
+                return ProgramSelector.PROGRAM_TYPE_DRMO;
+            case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
+            case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
+                return ProgramSelector.PROGRAM_TYPE_SXM;
+        }
+        if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
+                && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
+            return idType;
+        }
+        return ProgramSelector.PROGRAM_TYPE_INVALID;
+    }
+
+    private static int[] identifierTypesToProgramTypes(int[] idTypes) {
+        Set<Integer> programTypes = new ArraySet<>();
+
+        for (int i = 0; i < idTypes.length; i++) {
+            int pType = identifierTypeToProgramType(idTypes[i]);
+
+            if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
+
+            programTypes.add(pType);
+            if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
+                // TODO(b/69958423): verify AM/FM with region info
+                programTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
+            }
+            if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
+                // TODO(b/69958423): verify AM/FM with region info
+                programTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
+            }
+        }
+
+        int[] programTypesArray = new int[programTypes.size()];
+        int i = 0;
+        for (int programType : programTypes) {
+            programTypesArray[i++] = programType;
+        }
+        return programTypesArray;
+    }
+
+    private static RadioManager.BandDescriptor[] amfmConfigToBands(
+            @Nullable AmFmRegionConfig config) {
+        if (config == null) {
+            return new RadioManager.BandDescriptor[0];
+        }
+
+        int len = config.ranges.length;
+        List<RadioManager.BandDescriptor> bands = new ArrayList<>();
+
+        // Just a placeholder value.
+        int region = RadioManager.REGION_ITU_1;
+
+        for (int i = 0; i < len; i++) {
+            Utils.FrequencyBand bandType = Utils.getBand(config.ranges[i].lowerBound);
+            if (bandType == Utils.FrequencyBand.UNKNOWN) {
+                Slogf.e(TAG, "Unknown frequency band at %d kHz", config.ranges[i].lowerBound);
+                continue;
+            }
+            if (bandType == Utils.FrequencyBand.FM) {
+                bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
+                        config.ranges[i].lowerBound, config.ranges[i].upperBound,
+                        config.ranges[i].spacing,
+
+                        // TODO(b/69958777): stereo, rds, ta, af, ea
+                        /* stereo= */ true, /* rds= */ true, /* ta= */ true, /* af= */ true,
+                        /* ea= */ true
+                ));
+            } else {  // AM
+                bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
+                        config.ranges[i].lowerBound, config.ranges[i].upperBound,
+                        config.ranges[i].spacing,
+
+                        // TODO(b/69958777): stereo
+                        /* stereo= */ true
+                ));
+            }
+        }
+
+        return bands.toArray(RadioManager.BandDescriptor[]::new);
+    }
+
+    @Nullable
+    private static Map<String, Integer> dabConfigFromHalDabTableEntries(
+            @Nullable DabTableEntry[] config) {
+        if (config == null) {
+            return null;
+        }
+        Map<String, Integer> dabConfig = new ArrayMap<>();
+        for (int i = 0; i < config.length; i++) {
+            dabConfig.put(config[i].label, config[i].frequencyKhz);
+        }
+        return dabConfig;
+    }
+
+    static RadioManager.ModuleProperties propertiesFromHalProperties(int id,
+            String serviceName, Properties prop,
+            @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig) {
+        Objects.requireNonNull(serviceName);
+        Objects.requireNonNull(prop);
+
+        int[] supportedProgramTypes = identifierTypesToProgramTypes(prop.supportedIdentifierTypes);
+
+        return new RadioManager.ModuleProperties(
+                id,
+                serviceName,
+
+                // There is no Class concept in HAL AIDL.
+                RadioManager.CLASS_AM_FM,
+
+                prop.maker,
+                prop.product,
+                prop.version,
+                prop.serial,
+
+                // HAL AIDL only supports single tuner and audio source per
+                // HAL implementation instance.
+                /* numTuners= */ 1,
+                /* numAudioSources= */ 1,
+                /* isInitializationRequired= */ false,
+                /* isCaptureSupported= */ false,
+
+                amfmConfigToBands(amfmConfig),
+                /* isBgScanSupported= */ true,
+                supportedProgramTypes,
+                prop.supportedIdentifierTypes,
+                dabConfigFromHalDabTableEntries(dabConfig),
+                vendorInfoFromHalVendorKeyValues(prop.vendorInfo)
+        );
+    }
+
+    static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
+        ProgramIdentifier hwId = new ProgramIdentifier();
+        hwId.type = id.getType();
+        hwId.value = id.getValue();
+        return hwId;
+    }
+
+    @Nullable
+    static ProgramSelector.Identifier identifierFromHalProgramIdentifier(
+            ProgramIdentifier id) {
+        if (id.type == IdentifierType.INVALID) {
+            return null;
+        }
+        return new ProgramSelector.Identifier(id.type, id.value);
+    }
+
+    static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
+            ProgramSelector sel) {
+        android.hardware.broadcastradio.ProgramSelector hwSel =
+                new android.hardware.broadcastradio.ProgramSelector();
+
+        hwSel.primaryId = identifierToHalProgramIdentifier(sel.getPrimaryId());
+        ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
+        ArrayList<ProgramIdentifier> secondaryIdList = new ArrayList<>(secondaryIds.length);
+        for (int i = 0; i < secondaryIds.length; i++) {
+            secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
+        }
+        hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
+        return hwSel;
+    }
+
+    private static boolean isEmpty(
+            android.hardware.broadcastradio.ProgramSelector sel) {
+        return sel.primaryId.type == IdentifierType.INVALID && sel.primaryId.value == 0
+                && sel.secondaryIds.length == 0;
+    }
+
+    @Nullable
+    static ProgramSelector programSelectorFromHalProgramSelector(
+            android.hardware.broadcastradio.ProgramSelector sel) {
+        if (isEmpty(sel)) {
+            return null;
+        }
+
+        List<ProgramSelector.Identifier> secondaryIdList = new ArrayList<>();
+        for (int i = 0; i < sel.secondaryIds.length; i++) {
+            if (sel.secondaryIds[i] != null) {
+                secondaryIdList.add(identifierFromHalProgramIdentifier(sel.secondaryIds[i]));
+            }
+        }
+
+        return new ProgramSelector(
+                identifierTypeToProgramType(sel.primaryId.type),
+                Objects.requireNonNull(identifierFromHalProgramIdentifier(sel.primaryId)),
+                secondaryIdList.toArray(new ProgramSelector.Identifier[0]),
+                /* vendorIds= */ null);
+    }
+
+    private static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) {
+        RadioMetadata.Builder builder = new RadioMetadata.Builder();
+
+        for (int i = 0; i < meta.length; i++) {
+            switch (meta[i].getTag()) {
+                case Metadata.rdsPs:
+                    builder.putString(RadioMetadata.METADATA_KEY_RDS_PS, meta[i].getRdsPs());
+                    break;
+                case Metadata.rdsPty:
+                    builder.putInt(RadioMetadata.METADATA_KEY_RDS_PTY, meta[i].getRdsPty());
+                    break;
+                case Metadata.rbdsPty:
+                    builder.putInt(RadioMetadata.METADATA_KEY_RBDS_PTY, meta[i].getRbdsPty());
+                    break;
+                case Metadata.rdsRt:
+                    builder.putString(RadioMetadata.METADATA_KEY_RDS_RT, meta[i].getRdsRt());
+                    break;
+                case Metadata.songTitle:
+                    builder.putString(RadioMetadata.METADATA_KEY_TITLE, meta[i].getSongTitle());
+                    break;
+                case Metadata.songArtist:
+                    builder.putString(RadioMetadata.METADATA_KEY_ARTIST, meta[i].getSongArtist());
+                    break;
+                case Metadata.songAlbum:
+                    builder.putString(RadioMetadata.METADATA_KEY_ALBUM, meta[i].getSongAlbum());
+                    break;
+                case Metadata.stationIcon:
+                    builder.putInt(RadioMetadata.METADATA_KEY_ICON, meta[i].getStationIcon());
+                    break;
+                case Metadata.albumArt:
+                    builder.putInt(RadioMetadata.METADATA_KEY_ART, meta[i].getAlbumArt());
+                    break;
+                case Metadata.programName:
+                    builder.putString(RadioMetadata.METADATA_KEY_PROGRAM_NAME,
+                            meta[i].getProgramName());
+                    break;
+                case Metadata.dabEnsembleName:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME,
+                            meta[i].getDabEnsembleName());
+                    break;
+                case Metadata.dabEnsembleNameShort:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT,
+                            meta[i].getDabEnsembleNameShort());
+                    break;
+                case Metadata.dabServiceName:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME,
+                            meta[i].getDabServiceName());
+                    break;
+                case Metadata.dabServiceNameShort:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT,
+                            meta[i].getDabServiceNameShort());
+                    break;
+                case Metadata.dabComponentName:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME,
+                            meta[i].getDabComponentName());
+                    break;
+                case Metadata.dabComponentNameShort:
+                    builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT,
+                            meta[i].getDabComponentNameShort());
+                    break;
+                default:
+                    Slogf.w(TAG, "Ignored unknown metadata entry: %s", meta[i]);
+                    break;
+            }
+
+        }
+
+        return builder.build();
+    }
+
+    static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
+        Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
+        if (info.relatedContent != null) {
+            for (int i = 0; i < info.relatedContent.length; i++) {
+                ProgramSelector.Identifier relatedContentId =
+                        identifierFromHalProgramIdentifier(info.relatedContent[i]);
+                if (relatedContentId != null) {
+                    relatedContent.add(relatedContentId);
+                }
+            }
+        }
+
+        return new RadioManager.ProgramInfo(
+                Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
+                identifierFromHalProgramIdentifier(info.logicallyTunedTo),
+                identifierFromHalProgramIdentifier(info.physicallyTunedTo),
+                relatedContent,
+                info.infoFlags,
+                info.signalQuality,
+                radioMetadataFromHalMetadata(info.metadata),
+                vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+        );
+    }
+
+    static ProgramFilter filterToHalProgramFilter(@Nullable ProgramList.Filter filter) {
+        if (filter == null) {
+            filter = new ProgramList.Filter();
+        }
+
+        ProgramFilter hwFilter = new ProgramFilter();
+
+        IntArray identifierTypeList = new IntArray(filter.getIdentifierTypes().size());
+        ArrayList<ProgramIdentifier> identifiersList = new ArrayList<>();
+        Iterator<Integer> typeIterator = filter.getIdentifierTypes().iterator();
+        while (typeIterator.hasNext()) {
+            identifierTypeList.add(typeIterator.next());
+        }
+        Iterator<ProgramSelector.Identifier> idIterator = filter.getIdentifiers().iterator();
+        while (idIterator.hasNext()) {
+            identifiersList.add(identifierToHalProgramIdentifier(idIterator.next()));
+        }
+
+        hwFilter.identifierTypes = identifierTypeList.toArray();
+        hwFilter.identifiers = identifiersList.toArray(ProgramIdentifier[]::new);
+        hwFilter.includeCategories = filter.areCategoriesIncluded();
+        hwFilter.excludeModifications = filter.areModificationsExcluded();
+
+        return hwFilter;
+    }
+
+    static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
+        Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
+        for (int i = 0; i < chunk.modified.length; i++) {
+            modified.add(programInfoFromHalProgramInfo(chunk.modified[i]));
+        }
+        Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+        if (chunk.removed != null) {
+            for (int i = 0; i < chunk.removed.length; i++) {
+                ProgramSelector.Identifier removedId =
+                        identifierFromHalProgramIdentifier(chunk.removed[i]);
+                if (removedId != null) {
+                    removed.add(removedId);
+                }
+            }
+        }
+        return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
+    }
+
+    public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
+            Announcement hwAnnouncement) {
+        return new android.hardware.radio.Announcement(
+                Objects.requireNonNull(programSelectorFromHalProgramSelector(
+                        hwAnnouncement.selector)),
+                hwAnnouncement.type,
+                vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
+        );
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
new file mode 100644
index 0000000..095a5fa
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to filter and update program info for HAL clients from broadcast radio AIDL HAL
+ */
+final class ProgramInfoCache {
+
+    /**
+     * Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
+     * ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
+     * stays within the AIDL data size limit.
+     */
+    private static final int MAX_NUM_MODIFIED_PER_CHUNK = 100;
+
+    /**
+     * Maximum number of {@link ProgramSelector#Identifier} elements that will be put
+     * into the removed array of {@link ProgramList#Chunk}. Used to try to ensure a single
+     * {@link ProgramList#Chunk} stays within the AIDL data size limit.
+     */
+    private static final int MAX_NUM_REMOVED_PER_CHUNK = 500;
+
+    /**
+     * Map from primary identifier to corresponding {@link RadioManager#ProgramInfo}.
+     */
+    private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mProgramInfoMap =
+            new ArrayMap<>();
+
+    /**
+     * Flag indicating whether mProgramInfoMap is considered complete based upon the received
+     * updates.
+     */
+    private boolean mComplete = true;
+
+    /**
+     * Optional filter used in {@link ProgramInfoCache#filterAndUpdateFromInternal}. Usually this
+     * field is null for a HAL-side cache and non-null for an AIDL-side cache.
+     */
+    @Nullable private final ProgramList.Filter mFilter;
+
+    ProgramInfoCache(@Nullable ProgramList.Filter filter) {
+        mFilter = filter;
+    }
+
+    @VisibleForTesting
+    ProgramInfoCache(@Nullable ProgramList.Filter filter, boolean complete,
+            RadioManager.ProgramInfo... programInfos) {
+        mFilter = filter;
+        mComplete = complete;
+        for (int i = 0; i < programInfos.length; i++) {
+            mProgramInfoMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
+        }
+    }
+
+    @VisibleForTesting
+    boolean programInfosAreExactly(RadioManager.ProgramInfo... programInfos) {
+        Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> expectedMap = new ArrayMap<>();
+        for (int i = 0; i < programInfos.length; i++) {
+            expectedMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
+        }
+        return expectedMap.equals(mProgramInfoMap);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("ProgramInfoCache(mComplete = ");
+        sb.append(mComplete);
+        sb.append(", mFilter = ");
+        sb.append(mFilter);
+        sb.append(", mProgramInfoMap = [");
+        mProgramInfoMap.forEach((id, programInfo) -> {
+            sb.append(", ");
+            sb.append(programInfo);
+        });
+        return sb.append("])").toString();
+    }
+
+    public boolean isComplete() {
+        return mComplete;
+    }
+
+    @Nullable
+    public ProgramList.Filter getFilter() {
+        return mFilter;
+    }
+
+    @VisibleForTesting
+    void updateFromHalProgramListChunk(
+            android.hardware.broadcastradio.ProgramListChunk chunk) {
+        if (chunk.purge) {
+            mProgramInfoMap.clear();
+        }
+        for (int i = 0; i < chunk.modified.length; i++) {
+            RadioManager.ProgramInfo programInfo =
+                    ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+            mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+        }
+        if (chunk.removed != null) {
+            for (int i = 0; i < chunk.removed.length; i++) {
+                mProgramInfoMap.remove(
+                        ConversionUtils.identifierFromHalProgramIdentifier(chunk.removed[i]));
+            }
+        }
+        mComplete = chunk.complete;
+    }
+
+    List<ProgramList.Chunk> filterAndUpdateFromInternal(ProgramInfoCache other,
+            boolean purge) {
+        return filterAndUpdateFromInternal(other, purge, MAX_NUM_MODIFIED_PER_CHUNK,
+                MAX_NUM_REMOVED_PER_CHUNK);
+    }
+
+    @VisibleForTesting
+    List<ProgramList.Chunk> filterAndUpdateFromInternal(ProgramInfoCache other,
+            boolean purge, int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
+        if (purge) {
+            mProgramInfoMap.clear();
+        }
+        // If mProgramInfoMap is empty, we treat this update as a purge because this might be the
+        // first update to an AIDL client that changed its filter.
+        if (mProgramInfoMap.isEmpty()) {
+            purge = true;
+        }
+
+        Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+        Set<ProgramSelector.Identifier> removed = new ArraySet<>(mProgramInfoMap.keySet());
+        for (Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo> entry
+                : other.mProgramInfoMap.entrySet()) {
+            ProgramSelector.Identifier id = entry.getKey();
+            if (!passesFilter(id)) {
+                continue;
+            }
+            removed.remove(id);
+
+            RadioManager.ProgramInfo newInfo = entry.getValue();
+            if (!shouldIncludeInModified(newInfo)) {
+                continue;
+            }
+            mProgramInfoMap.put(id, newInfo);
+            modified.add(newInfo);
+        }
+        for (ProgramSelector.Identifier rem : removed) {
+            mProgramInfoMap.remove(rem);
+        }
+        mComplete = other.mComplete;
+        return buildChunks(purge, mComplete, modified, maxNumModifiedPerChunk, removed,
+                maxNumRemovedPerChunk);
+    }
+
+    @Nullable
+    List<ProgramList.Chunk> filterAndApplyChunk(ProgramList.Chunk chunk) {
+        return filterAndApplyChunkInternal(chunk, MAX_NUM_MODIFIED_PER_CHUNK,
+                MAX_NUM_REMOVED_PER_CHUNK);
+    }
+
+    @VisibleForTesting
+    @Nullable
+    List<ProgramList.Chunk> filterAndApplyChunkInternal(ProgramList.Chunk chunk,
+            int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
+        if (chunk.isPurge()) {
+            mProgramInfoMap.clear();
+        }
+
+        Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+        Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+        for (RadioManager.ProgramInfo info : chunk.getModified()) {
+            ProgramSelector.Identifier id = info.getSelector().getPrimaryId();
+            if (!passesFilter(id) || !shouldIncludeInModified(info)) {
+                continue;
+            }
+            mProgramInfoMap.put(id, info);
+            modified.add(info);
+        }
+        for (ProgramSelector.Identifier id : chunk.getRemoved()) {
+            if (mProgramInfoMap.containsKey(id)) {
+                mProgramInfoMap.remove(id);
+                removed.add(id);
+            }
+        }
+        if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()
+                && !chunk.isPurge()) {
+            return null;
+        }
+        mComplete = chunk.isComplete();
+        return buildChunks(chunk.isPurge(), mComplete, modified, maxNumModifiedPerChunk, removed,
+                maxNumRemovedPerChunk);
+    }
+
+    private boolean passesFilter(ProgramSelector.Identifier id) {
+        if (mFilter == null) {
+            return true;
+        }
+        if (!mFilter.getIdentifierTypes().isEmpty()
+                && !mFilter.getIdentifierTypes().contains(id.getType())) {
+            return false;
+        }
+        if (!mFilter.getIdentifiers().isEmpty() && !mFilter.getIdentifiers().contains(id)) {
+            return false;
+        }
+        return mFilter.areCategoriesIncluded() || !id.isCategoryType();
+    }
+
+    private boolean shouldIncludeInModified(RadioManager.ProgramInfo newInfo) {
+        RadioManager.ProgramInfo oldInfo = mProgramInfoMap.get(
+                newInfo.getSelector().getPrimaryId());
+        if (oldInfo == null) {
+            return true;
+        }
+        if (mFilter != null && mFilter.areModificationsExcluded()) {
+            return false;
+        }
+        return !oldInfo.equals(newInfo);
+    }
+
+    private static int roundUpFraction(int numerator, int denominator) {
+        return (numerator / denominator) + (numerator % denominator > 0 ? 1 : 0);
+    }
+
+    private static List<ProgramList.Chunk> buildChunks(boolean purge, boolean complete,
+            @Nullable Collection<RadioManager.ProgramInfo> modified, int maxNumModifiedPerChunk,
+            @Nullable Collection<ProgramSelector.Identifier> removed, int maxNumRemovedPerChunk) {
+        // Communication protocol requires that if purge is set, removed is empty.
+        if (purge) {
+            removed = null;
+        }
+
+        // Determine number of chunks we need to send.
+        int numChunks = purge ? 1 : 0;
+        if (modified != null) {
+            numChunks = Math.max(numChunks,
+                    roundUpFraction(modified.size(), maxNumModifiedPerChunk));
+        }
+        if (removed != null) {
+            numChunks = Math.max(numChunks, roundUpFraction(removed.size(), maxNumRemovedPerChunk));
+        }
+        if (numChunks == 0) {
+            return new ArrayList<>();
+        }
+
+        // Try to make similarly-sized chunks by evenly distributing elements from modified and
+        // removed among them.
+        int modifiedPerChunk = 0;
+        int removedPerChunk = 0;
+        Iterator<RadioManager.ProgramInfo> modifiedIter = null;
+        Iterator<ProgramSelector.Identifier> removedIter = null;
+        if (modified != null) {
+            modifiedPerChunk = roundUpFraction(modified.size(), numChunks);
+            modifiedIter = modified.iterator();
+        }
+        if (removed != null) {
+            removedPerChunk = roundUpFraction(removed.size(), numChunks);
+            removedIter = removed.iterator();
+        }
+        List<ProgramList.Chunk> chunks = new ArrayList<>(numChunks);
+        for (int i = 0; i < numChunks; i++) {
+            ArraySet<RadioManager.ProgramInfo> modifiedChunk = new ArraySet<>();
+            ArraySet<ProgramSelector.Identifier> removedChunk = new ArraySet<>();
+            if (modifiedIter != null) {
+                for (int j = 0; j < modifiedPerChunk && modifiedIter.hasNext(); j++) {
+                    modifiedChunk.add(modifiedIter.next());
+                }
+            }
+            if (removedIter != null) {
+                for (int j = 0; j < removedPerChunk && removedIter.hasNext(); j++) {
+                    removedChunk.add(removedIter.next());
+                }
+            }
+            chunks.add(new ProgramList.Chunk(purge && i == 0, complete && (i == numChunks - 1),
+                      modifiedChunk, removedChunk));
+        }
+        return chunks;
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java
new file mode 100644
index 0000000..cca351b
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioLogger.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.server.utils.Slogf;
+
+/**
+ * Event logger to log and dump events of radio module and tuner session
+ * for AIDL broadcast radio HAL
+ */
+final class RadioLogger {
+    private final String mTag;
+    private final boolean mDebug;
+    private final LocalLog mEventLogger;
+
+    RadioLogger(String tag, int loggerQueueSize) {
+        mTag = tag;
+        mDebug = Log.isLoggable(mTag, Log.DEBUG);
+        mEventLogger = new LocalLog(loggerQueueSize);
+    }
+
+    void logRadioEvent(String logFormat, Object... args) {
+        String log = TextUtils.formatSimple(logFormat, args);
+        mEventLogger.log(log);
+        if (mDebug) {
+            Slogf.d(mTag, logFormat, args);
+        }
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        mEventLogger.dump(pw);
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
new file mode 100644
index 0000000..c6dc431
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.Announcement;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IAnnouncementListener;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.broadcastradio.ICloseHandle;
+import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.RadioManager;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+final class RadioModule {
+    private static final String TAG = "BcRadioAidlSrv.module";
+    private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
+
+    private final IBroadcastRadio mService;
+    public final RadioManager.ModuleProperties mProperties;
+
+    private final Object mLock;
+    private final Handler mHandler;
+    private final RadioLogger mLogger;
+
+    /**
+     * Tracks antenna state reported by HAL (if any).
+     */
+    @GuardedBy("mLock")
+    private Boolean mAntennaConnected;
+
+    @GuardedBy("mLock")
+    private RadioManager.ProgramInfo mCurrentProgramInfo;
+
+    @GuardedBy("mLock")
+    private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null);
+
+    @GuardedBy("mLock")
+    private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters;
+
+    /**
+     * Set of active AIDL tuner sessions created through openSession().
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<TunerSession> mAidlTunerSessions = new ArraySet<>();
+
+    /**
+     * Callback registered with the HAL to relay callbacks to AIDL clients.
+     */
+    private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
+
+        public void onTuneFailed(int result, ProgramSelector programSelector) {
+            fireLater(() -> {
+                synchronized (mLock) {
+                    android.hardware.radio.ProgramSelector csel =
+                            ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
+                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                }
+            });
+        }
+
+        @Override
+        public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
+            fireLater(() -> {
+                synchronized (mLock) {
+                    mCurrentProgramInfo =
+                            ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+                    RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+                    fanoutAidlCallbackLocked(cb -> {
+                        cb.onCurrentProgramInfoChanged(currentProgramInfo);
+                    });
+                }
+            });
+        }
+
+        @Override
+        public void onProgramListUpdated(ProgramListChunk programListChunk) {
+            fireLater(() -> {
+                synchronized (mLock) {
+                    android.hardware.radio.ProgramList.Chunk chunk =
+                            ConversionUtils.chunkFromHalProgramListChunk(programListChunk);
+                    mProgramInfoCache.filterAndApplyChunk(chunk);
+
+                    for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+                        mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal(chunk);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onAntennaStateChange(boolean connected) {
+            fireLater(() -> {
+                synchronized (mLock) {
+                    mAntennaConnected = connected;
+                    fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+                }
+            });
+        }
+
+        @Override
+        public void onConfigFlagUpdated(int flag, boolean value) {
+            fireLater(() -> {
+                // TODO(b/243853343): implement config flag update method in
+                //  android.hardware.radio.ITunerCallback
+            });
+        }
+
+        @Override
+        public void onParametersUpdated(VendorKeyValue[] parameters) {
+            fireLater(() -> {
+                synchronized (mLock) {
+                    Map<String, String> cparam =
+                            ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
+                    fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+                }
+            });
+        }
+    };
+
+    @VisibleForTesting
+    RadioModule(IBroadcastRadio service,
+            RadioManager.ModuleProperties properties, Object lock) {
+        mProperties = Objects.requireNonNull(properties, "properties cannot be null");
+        mService = Objects.requireNonNull(service, "service cannot be null");
+        mLock = Objects.requireNonNull(lock, "lock cannot be null");
+        mHandler = new Handler(Looper.getMainLooper());
+        mLogger = new RadioLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
+    }
+
+    @Nullable
+    public static RadioModule tryLoadingModule(int moduleId, String moduleName,
+            IBinder serviceBinder, Object lock) {
+        try {
+            Slogf.i(TAG, "Try loading module for module id = %d, module name = %s",
+                    moduleId, moduleName);
+            IBroadcastRadio service = IBroadcastRadio.Stub
+                    .asInterface(serviceBinder);
+            if (service == null) {
+                Slogf.w(TAG, "Module %s is null", moduleName);
+                return null;
+            }
+
+            AmFmRegionConfig amfmConfig;
+            try {
+                amfmConfig = service.getAmFmRegionConfig(/* full= */ false);
+            } catch (RuntimeException ex) {
+                Slogf.i(TAG, "Module %s does not has AMFM config", moduleName);
+                amfmConfig = null;
+            }
+
+            DabTableEntry[] dabConfig;
+            try {
+                dabConfig = service.getDabRegionConfig();
+            } catch (RuntimeException ex) {
+                Slogf.i(TAG, "Module %s does not has DAB config", moduleName);
+                dabConfig = null;
+            }
+
+            RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties(
+                    moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig);
+
+            return new RadioModule(service, prop, lock);
+        } catch (RemoteException ex) {
+            Slogf.e(TAG, ex, "Failed to load module %s", moduleName);
+            return null;
+        }
+    }
+
+    public IBroadcastRadio getService() {
+        return mService;
+    }
+
+    void setInternalHalCallback() throws RemoteException {
+        synchronized (mLock) {
+            mService.setTunerCallback(mHalTunerCallback);
+        }
+    }
+
+    public TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+            throws RemoteException {
+        mLogger.logRadioEvent("Open TunerSession");
+        TunerSession tunerSession;
+        Boolean antennaConnected;
+        RadioManager.ProgramInfo currentProgramInfo;
+        synchronized (mLock) {
+            tunerSession = new TunerSession(this, mService, userCb, mLock);
+            mAidlTunerSessions.add(tunerSession);
+            antennaConnected = mAntennaConnected;
+            currentProgramInfo = mCurrentProgramInfo;
+        }
+        // Propagate state to new client.
+        // Note: These callbacks are invoked while holding mLock to prevent race conditions
+        // with new callbacks from the HAL.
+        if (antennaConnected != null) {
+            userCb.onAntennaState(antennaConnected);
+        }
+        if (currentProgramInfo != null) {
+            userCb.onCurrentProgramInfoChanged(currentProgramInfo);
+        }
+
+        return tunerSession;
+    }
+
+    public void closeSessions(int error) {
+        mLogger.logRadioEvent("Close TunerSessions %d", error);
+        // TunerSession.close() must be called without mAidlTunerSessions locked because
+        // it can call onTunerSessionClosed(). Therefore, the contents of mAidlTunerSessions
+        // are copied into a local array here.
+        TunerSession[] tunerSessions;
+        synchronized (mLock) {
+            tunerSessions = new TunerSession[mAidlTunerSessions.size()];
+            mAidlTunerSessions.toArray(tunerSessions);
+            mAidlTunerSessions.clear();
+        }
+
+        for (TunerSession tunerSession : tunerSessions) {
+            try {
+                tunerSession.close(error);
+            } catch (Exception e) {
+                Slogf.e(TAG, "Failed to close TunerSession %s: %s", tunerSession, e);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private android.hardware.radio.ProgramList.Filter
+            buildUnionOfTunerSessionFiltersLocked() {
+        Set<Integer> idTypes = null;
+        Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
+        boolean includeCategories = false;
+        boolean excludeModifications = true;
+
+        for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+            android.hardware.radio.ProgramList.Filter filter =
+                    mAidlTunerSessions.valueAt(i).getProgramListFilter();
+            if (filter == null) {
+                continue;
+            }
+
+            if (idTypes == null) {
+                idTypes = new ArraySet<>(filter.getIdentifierTypes());
+                ids = new ArraySet<>(filter.getIdentifiers());
+                includeCategories = filter.areCategoriesIncluded();
+                excludeModifications = filter.areModificationsExcluded();
+                continue;
+            }
+            if (!idTypes.isEmpty()) {
+                if (filter.getIdentifierTypes().isEmpty()) {
+                    idTypes.clear();
+                } else {
+                    idTypes.addAll(filter.getIdentifierTypes());
+                }
+            }
+
+            if (!ids.isEmpty()) {
+                if (filter.getIdentifiers().isEmpty()) {
+                    ids.clear();
+                } else {
+                    ids.addAll(filter.getIdentifiers());
+                }
+            }
+
+            includeCategories |= filter.areCategoriesIncluded();
+            excludeModifications &= filter.areModificationsExcluded();
+        }
+
+        return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids,
+                includeCategories, excludeModifications);
+    }
+
+    void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) {
+        synchronized (mLock) {
+            onTunerSessionProgramListFilterChangedLocked(session);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {
+        android.hardware.radio.ProgramList.Filter newFilter =
+                buildUnionOfTunerSessionFiltersLocked();
+        if (newFilter == null) {
+            // If there are no AIDL clients remaining, we can stop updates from the HAL as well.
+            if (mUnionOfAidlProgramFilters == null) {
+                return;
+            }
+            mUnionOfAidlProgramFilters = null;
+            try {
+                mService.stopProgramListUpdates();
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "mHalTunerSession.stopProgramListUpdates() failed");
+            }
+            return;
+        }
+
+        synchronized (mLock) {
+            // If the HAL filter doesn't change, we can immediately send an update to the AIDL
+            // client.
+            if (newFilter.equals(mUnionOfAidlProgramFilters)) {
+                if (session != null) {
+                    session.updateProgramInfoFromHalCache(mProgramInfoCache);
+                }
+                return;
+            }
+
+            // Otherwise, update the HAL's filter, and AIDL clients will be updated when
+            // mHalTunerCallback.onProgramListUpdated() is called.
+            mUnionOfAidlProgramFilters = newFilter;
+            try {
+                mService.startProgramListUpdates(
+                        ConversionUtils.filterToHalProgramFilter(newFilter));
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates");
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed");
+            }
+        }
+    }
+
+    void onTunerSessionClosed(TunerSession tunerSession) {
+        synchronized (mLock) {
+            onTunerSessionsClosedLocked(tunerSession);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) {
+        for (TunerSession tunerSession : tunerSessions) {
+            mAidlTunerSessions.remove(tunerSession);
+        }
+        onTunerSessionProgramListFilterChanged(null);
+    }
+
+    // add to mHandler queue
+    private void fireLater(Runnable r) {
+        mHandler.post(() -> r.run());
+    }
+
+    interface AidlCallbackRunnable {
+        void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+    }
+
+    // Invokes runnable with each TunerSession currently open.
+    void fanoutAidlCallback(AidlCallbackRunnable runnable) {
+        fireLater(() -> {
+            synchronized (mLock) {
+                fanoutAidlCallbackLocked(runnable);
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
+        List<TunerSession> deadSessions = null;
+        for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+            try {
+                runnable.run(mAidlTunerSessions.valueAt(i).mCallback);
+            } catch (DeadObjectException ex) {
+                // The other side died without calling close(), so just purge it from our records.
+                Slogf.e(TAG, "Removing dead TunerSession");
+                if (deadSessions == null) {
+                    deadSessions = new ArrayList<>();
+                }
+                deadSessions.add(mAidlTunerSessions.valueAt(i));
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, ex, "Failed to invoke ITunerCallback");
+            }
+        }
+        if (deadSessions != null) {
+            onTunerSessionsClosedLocked(deadSessions.toArray(
+                    new TunerSession[deadSessions.size()]));
+        }
+    }
+
+    public android.hardware.radio.ICloseHandle addAnnouncementListener(
+            android.hardware.radio.IAnnouncementListener listener,
+            int[] enabledTypes) throws RemoteException {
+        mLogger.logRadioEvent("Add AnnouncementListener");
+        byte[] enabledList = new byte[enabledTypes.length];
+        for (int index = 0; index < enabledList.length; index++) {
+            enabledList[index] = (byte) enabledTypes[index];
+        }
+
+        final ICloseHandle[] hwCloseHandle = {null};
+        IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+
+            public String getInterfaceHash() {
+                return this.HASH;
+            }
+
+            public void onListUpdated(Announcement[] hwAnnouncements)
+                    throws RemoteException {
+                List<android.hardware.radio.Announcement> announcements =
+                        new ArrayList<>(hwAnnouncements.length);
+                for (int i = 0; i < hwAnnouncements.length; i++) {
+                    announcements.add(
+                            ConversionUtils.announcementFromHalAnnouncement(hwAnnouncements[i]));
+                }
+                listener.onListUpdated(announcements);
+            }
+        };
+
+        synchronized (mLock) {
+            try {
+                hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList);
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener");
+            }
+        }
+
+        return new android.hardware.radio.ICloseHandle.Stub() {
+            public void close() {
+                try {
+                    hwCloseHandle[0].close();
+                } catch (RemoteException ex) {
+                    Slogf.e(TAG, ex, "Failed closing announcement listener");
+                }
+                hwCloseHandle[0] = null;
+            }
+        };
+    }
+
+    Bitmap getImage(int id) {
+        mLogger.logRadioEvent("Get image for id = %d", id);
+        if (id == 0) throw new IllegalArgumentException("Image ID is missing");
+
+        byte[] rawImage;
+        synchronized (mLock) {
+            try {
+                rawImage = mService.getImage(id);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        if (rawImage == null || rawImage.length == 0) return null;
+
+        return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
+    }
+
+    void dumpInfo(IndentingPrintWriter pw) {
+        pw.printf("RadioModule\n");
+
+        pw.increaseIndent();
+        synchronized (mLock) {
+            pw.printf("BroadcastRadioServiceImpl: %s\n", mService);
+            pw.printf("Properties: %s\n", mProperties);
+            pw.printf("Antenna state: ");
+            if (mAntennaConnected == null) {
+                pw.printf("undetermined\n");
+            } else {
+                pw.printf("%s\n", mAntennaConnected ? "connected" : "not connected");
+            }
+            pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo);
+            pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
+            pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters);
+            pw.printf("AIDL TunerSessions [%d]:\n", mAidlTunerSessions.size());
+
+            pw.increaseIndent();
+            for (int i = 0; i < mAidlTunerSessions.size(); i++) {
+                mAidlTunerSessions.valueAt(i).dumpInfo(pw);
+            }
+            pw.decreaseIndent();
+        }
+        pw.printf("Radio module events:\n");
+
+        pw.increaseIndent();
+        mLogger.dump(pw);
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
new file mode 100644
index 0000000..7c26a87
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.ConfigFlag;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+final class TunerSession extends ITuner.Stub {
+    private static final String TAG = "BcRadioAidlSrv.session";
+    private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
+
+    private final Object mLock;
+
+    private final RadioLogger mLogger;
+    private final RadioModule mModule;
+    final android.hardware.radio.ITunerCallback mCallback;
+    private final IBroadcastRadio mService;
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
+    @GuardedBy("mLock")
+    private boolean mIsMuted;
+    @GuardedBy("mLock")
+    private ProgramInfoCache mProgramInfoCache;
+
+    // necessary only for older APIs compatibility
+    @GuardedBy("mLock")
+    private RadioManager.BandConfig mPlaceHolderConfig;
+
+    TunerSession(RadioModule radioModule, IBroadcastRadio service,
+            android.hardware.radio.ITunerCallback callback,
+            Object lock) {
+        mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
+        mService = Objects.requireNonNull(service, "service cannot be null");
+        mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+        mLock = Objects.requireNonNull(lock, "lock cannot be null");
+        mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
+    }
+
+    @Override
+    public void close() {
+        mLogger.logRadioEvent("Close tuner session");
+        close(null);
+    }
+
+    /**
+     * Closes the TunerSession. If error is non-null, the client's onError() callback is invoked
+     * first with the specified error, see {@link
+     * android.hardware.radio.RadioTuner.Callback#onError}.
+     *
+     * @param error Error to send to client before session is closed. If null, there is no error
+     *              when closing the session.
+     */
+    public void close(@Nullable Integer error) {
+        if (error == null) {
+            mLogger.logRadioEvent("Close tuner session on error null");
+        } else {
+            mLogger.logRadioEvent("Close tuner session on error %d", error);
+        }
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            if (error != null) {
+                try {
+                    mCallback.onError(error);
+                } catch (RemoteException ex) {
+                    Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error);
+                }
+            }
+            mIsClosed = true;
+            mModule.onTunerSessionClosed(this);
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        synchronized (mLock) {
+            return mIsClosed;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void checkNotClosedLocked() {
+        if (mIsClosed) {
+            throw new IllegalStateException("Tuner is closed, no further operations are allowed");
+        }
+    }
+
+    @Override
+    public void setConfiguration(RadioManager.BandConfig config) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
+        }
+        Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
+        mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
+    }
+
+    @Override
+    public RadioManager.BandConfig getConfiguration() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return mPlaceHolderConfig;
+        }
+    }
+
+    @Override
+    public void setMuted(boolean mute) {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            if (mIsMuted == mute) return;
+            mIsMuted = mute;
+        }
+        Slogf.w(TAG, "Mute %b via RadioService is not implemented - please handle it via app",
+                mute);
+    }
+
+    @Override
+    public boolean isMuted() {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            return mIsMuted;
+        }
+    }
+
+    @Override
+    public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
+                directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                mService.step(!directionDown);
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "step");
+            }
+        }
+    }
+
+    @Override
+    public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+                directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                mService.seek(!directionDown, skipSubChannel);
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "seek");
+            }
+        }
+    }
+
+    @Override
+    public void tune(ProgramSelector selector) throws RemoteException {
+        mLogger.logRadioEvent("Tune with selector %s", selector);
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                mService.tune(ConversionUtils.programSelectorToHalProgramSelector(selector));
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "tune");
+            }
+        }
+    }
+
+    @Override
+    public void cancel() {
+        Slogf.i(TAG, "Cancel");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                mService.cancel();
+            } catch (RemoteException ex) {
+                Slogf.e(TAG, "Failed to cancel tuner session");
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    @Override
+    public void cancelAnnouncement() {
+        // TODO(b/244485175): deperacte cancelAnnouncement
+        Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
+    }
+
+    @Override
+    public Bitmap getImage(int id) {
+        mLogger.logRadioEvent("Get image for %d", id);
+        return mModule.getImage(id);
+    }
+
+    @Override
+    public boolean startBackgroundScan() {
+        Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+        mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
+        return true;
+    }
+
+    @Override
+    public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+        mLogger.logRadioEvent("Start programList updates %s", filter);
+        // If the AIDL client provides a null filter, it wants all updates, so use the most broad
+        // filter.
+        if (filter == null) {
+            filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+                    /* includeCategories= */ true,
+                    /* excludeModifications= */ false);
+        }
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            mProgramInfoCache = new ProgramInfoCache(filter);
+        }
+        // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
+        // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
+        mModule.onTunerSessionProgramListFilterChanged(this);
+    }
+
+    ProgramList.Filter getProgramListFilter() {
+        synchronized (mLock) {
+            return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
+        }
+    }
+
+    void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) {
+        List<ProgramList.Chunk> clientUpdateChunks;
+        synchronized (mLock) {
+            if (mProgramInfoCache == null) {
+                return;
+            }
+            clientUpdateChunks = mProgramInfoCache.filterAndApplyChunk(mergedChunk);
+        }
+        dispatchClientUpdateChunks(clientUpdateChunks);
+    }
+
+    void updateProgramInfoFromHalCache(ProgramInfoCache halCache) {
+        List<ProgramList.Chunk> clientUpdateChunks;
+        synchronized (mLock) {
+            if (mProgramInfoCache == null) {
+                return;
+            }
+            clientUpdateChunks = mProgramInfoCache.filterAndUpdateFromInternal(
+                    halCache, /* purge = */ true);
+        }
+        dispatchClientUpdateChunks(clientUpdateChunks);
+    }
+
+    private void dispatchClientUpdateChunks(@Nullable List<ProgramList.Chunk> chunks) {
+        if (chunks == null) {
+            return;
+        }
+        for (int i = 0; i < chunks.size(); i++) {
+            try {
+                mCallback.onProgramListUpdated(chunks.get(i));
+            } catch (RemoteException ex) {
+                Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed");
+            }
+        }
+    }
+
+    @Override
+    public void stopProgramListUpdates() throws RemoteException {
+        mLogger.logRadioEvent("Stop programList updates");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            mProgramInfoCache = null;
+        }
+        // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
+        // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
+        mModule.onTunerSessionProgramListFilterChanged(this);
+    }
+
+    @Override
+    public boolean isConfigFlagSupported(int flag) {
+        try {
+            isConfigFlagSet(flag);
+            return true;
+        } catch (IllegalStateException | UnsupportedOperationException ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isConfigFlagSet(int flag) {
+        mLogger.logRadioEvent("is ConfigFlag %s set? ", ConfigFlag.$.toString(flag));
+        synchronized (mLock) {
+            checkNotClosedLocked();
+
+            try {
+                return mService.isConfigFlagSet(flag);
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "isConfigFlagSet");
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Failed to check flag " + ConfigFlag.$.toString(flag),
+                        ex);
+            }
+        }
+    }
+
+    @Override
+    public void setConfigFlag(int flag, boolean value) throws RemoteException {
+        mLogger.logRadioEvent("set ConfigFlag %s to %b ",
+                ConfigFlag.$.toString(flag), value);
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                mService.setConfigFlag(flag, value);
+            } catch (RuntimeException ex) {
+                throw ConversionUtils.throwOnError(ex, /* action= */ "setConfigFlag");
+            }
+        }
+    }
+
+    @Override
+    public Map<String, String> setParameters(Map<String, String> parameters) {
+        mLogger.logRadioEvent("Set parameters ");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                return ConversionUtils.vendorInfoFromHalVendorKeyValues(mService.setParameters(
+                        ConversionUtils.vendorInfoToHalVendorKeyValues(parameters)));
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    @Override
+    public Map<String, String> getParameters(List<String> keys) {
+        mLogger.logRadioEvent("Get parameters ");
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            try {
+                return ConversionUtils.vendorInfoFromHalVendorKeyValues(
+                        mService.getParameters(keys.toArray(new String[0])));
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    void dumpInfo(IndentingPrintWriter pw) {
+        pw.printf("TunerSession\n");
+
+        pw.increaseIndent();
+        synchronized (mLock) {
+            pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
+            pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No");
+            pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
+            pw.printf("Config: %s\n", mPlaceHolderConfig);
+        }
+        pw.printf("Tuner session events:\n");
+
+        pw.increaseIndent();
+        mLogger.dump(pw);
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/Utils.java b/services/core/java/com/android/server/broadcastradio/aidl/Utils.java
new file mode 100644
index 0000000..b6bea5b
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/aidl/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+final class Utils {
+
+    private Utils() {
+        throw new UnsupportedOperationException("Utils class is noninstantiable");
+    }
+
+    public enum FrequencyBand {
+        UNKNOWN,
+        FM,
+        AM_LW,
+        AM_MW,
+        AM_SW,
+    }
+
+    static FrequencyBand getBand(int freq) {
+        // keep in sync with hardware/interfaces/broadcastradio/common/utilsaidl/Utils.cpp
+        if (freq < 30) return FrequencyBand.UNKNOWN;
+        if (freq < 500) return FrequencyBand.AM_LW;
+        if (freq < 1705) return FrequencyBand.AM_MW;
+        if (freq < 30000) return FrequencyBand.AM_SW;
+        if (freq < 60000) return FrequencyBand.UNKNOWN;
+        if (freq < 110000) return FrequencyBand.FM;
+        return FrequencyBand.UNKNOWN;
+    }
+
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 676dc19..6a53978 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3210,41 +3210,44 @@
                 return;
             }
 
-            if (mSession != null && mMobikeEnabled) {
-                Log.d(
-                        TAG,
-                        "IKE Session has mobility. Delay handleSessionLost for losing network "
-                                + network
-                                + " on session with token "
-                                + mCurrentToken);
+            Log.d(TAG, "Schedule a delay handleSessionLost for losing network "
+                            + network
+                            + " on session with token "
+                            + mCurrentToken);
 
-                final int token = mCurrentToken;
-                // Delay the teardown in case a new network will be available soon. For example,
-                // during handover between two WiFi networks, Android will disconnect from the
-                // first WiFi and then connects to the second WiFi.
-                mScheduledHandleNetworkLostFuture =
-                        mExecutor.schedule(
-                                () -> {
-                                    if (isActiveToken(token)) {
-                                        handleSessionLost(null /* exception */, network);
-                                    } else {
-                                        Log.d(
-                                                TAG,
-                                                "Scheduled handleSessionLost fired for "
-                                                        + "obsolete token "
-                                                        + token);
+            final int token = mCurrentToken;
+            // Delay the teardown in case a new network will be available soon. For example,
+            // during handover between two WiFi networks, Android will disconnect from the
+            // first WiFi and then connects to the second WiFi.
+            mScheduledHandleNetworkLostFuture =
+                    mExecutor.schedule(
+                            () -> {
+                                if (isActiveToken(token)) {
+                                    handleSessionLost(new IkeNetworkLostException(network),
+                                            network);
+
+                                    synchronized (Vpn.this) {
+                                        // Ignore stale runner.
+                                        if (mVpnRunner != this) return;
+
+                                        updateState(DetailedState.DISCONNECTED,
+                                                "Network lost");
                                     }
+                                } else {
+                                    Log.d(
+                                            TAG,
+                                            "Scheduled handleSessionLost fired for "
+                                                    + "obsolete token "
+                                                    + token);
+                                }
 
-                                    // Reset mScheduledHandleNetworkLostFuture since it's
-                                    // already run on executor thread.
-                                    mScheduledHandleNetworkLostFuture = null;
-                                },
-                                NETWORK_LOST_TIMEOUT_MS,
-                                TimeUnit.MILLISECONDS);
-            } else {
-                Log.d(TAG, "Call handleSessionLost for losing network " + network);
-                handleSessionLost(null /* exception */, network);
-            }
+                                // Reset mScheduledHandleNetworkLostFuture since it's
+                                // already run on executor thread.
+                                mScheduledHandleNetworkLostFuture = null;
+                            },
+                            NETWORK_LOST_TIMEOUT_MS,
+                            TimeUnit.MILLISECONDS);
+
         }
 
         private void cancelHandleNetworkLostTimeout() {
@@ -4185,8 +4188,6 @@
      */
     @NonNull
     public synchronized List<String> getAppExclusionList(@NonNull String packageName) {
-        enforceNotRestrictedUser();
-
         final long oldId = Binder.clearCallingIdentity();
         try {
             final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName));
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 2c6257f..5c679b8 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -2083,7 +2083,7 @@
             try (FileInputStream in = mStatusFile.openRead()) {
                 readStatusInfoLocked(in);
             }
-        } catch (IOException e) {
+        } catch (Exception e) {
             Slog.e(TAG, "Unable to read status info file.", e);
         }
     }
@@ -2483,7 +2483,7 @@
             try (FileInputStream in = mStatisticsFile.openRead()) {
                 readDayStatsLocked(in);
             }
-        } catch (IOException e) {
+        } catch (Exception e) {
             Slog.e(TAG, "Unable to read day stats file.", e);
         }
     }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8e00ccf..44c8e18 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -21,8 +21,11 @@
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -106,6 +109,8 @@
     private final BinderService mBinderService;
     @NonNull
     private final OverrideRequestController mOverrideRequestController;
+    @NonNull
+    private final DeviceStateProviderListener mDeviceStateProviderListener;
     @VisibleForTesting
     @NonNull
     public ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -139,6 +144,12 @@
     @NonNull
     private Optional<OverrideRequest> mActiveOverride = Optional.empty();
 
+    // The current active base state override request. When set the device state specified here will
+    // replace the value in mBaseState.
+    @GuardedBy("mLock")
+    @NonNull
+    private Optional<OverrideRequest> mActiveBaseStateOverride = Optional.empty();
+
     // List of processes registered to receive notifications about changes to device state and
     // request status indexed by process id.
     @GuardedBy("mLock")
@@ -177,7 +188,8 @@
         mOverrideRequestController = new OverrideRequestController(
                 this::onOverrideRequestStatusChangedLocked);
         mDeviceStatePolicy = policy;
-        mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+        mDeviceStateProviderListener = new DeviceStateProviderListener();
+        mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
     }
@@ -257,6 +269,21 @@
         }
     }
 
+    /**
+     * Returns the current override base state, or {@link Optional#empty()} if no override state is
+     * requested. If an override base state is present, the returned state will be the same as
+     * the base state returned from {@link #getBaseState()}.
+     */
+    @NonNull
+    Optional<DeviceState> getOverrideBaseState() {
+        synchronized (mLock) {
+            if (mActiveBaseStateOverride.isPresent()) {
+                return getStateLocked(mActiveBaseStateOverride.get().getRequestedState());
+            }
+            return Optional.empty();
+        }
+    }
+
     /** Returns the list of currently supported device states. */
     DeviceState[] getSupportedStates() {
         synchronized (mLock) {
@@ -366,6 +393,7 @@
             }
 
             final DeviceState baseState = baseStateOptional.get();
+
             if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) {
                 // Base state hasn't changed. Nothing to do.
                 return;
@@ -375,7 +403,7 @@
             if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
                 mOverrideRequestController.cancelOverrideRequest();
             }
-            mOverrideRequestController.handleBaseStateChanged();
+            mOverrideRequestController.handleBaseStateChanged(identifier);
             updatePendingStateLocked();
 
             if (!mPendingState.isPresent()) {
@@ -528,16 +556,41 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
             @OverrideRequestController.RequestStatus int status) {
-        if (status == STATUS_ACTIVE) {
-            mActiveOverride = Optional.of(request);
-        } else if (status == STATUS_CANCELED) {
-            if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
-                mActiveOverride = Optional.empty();
+        if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+            switch (status) {
+                case STATUS_ACTIVE:
+                    enableBaseStateRequestLocked(request);
+                    return;
+                case STATUS_CANCELED:
+                    if (mActiveBaseStateOverride.isPresent()
+                            && mActiveBaseStateOverride.get() == request) {
+                        mActiveBaseStateOverride = Optional.empty();
+                    }
+                    break;
+                case STATUS_UNKNOWN: // same as default
+                default:
+                    throw new IllegalArgumentException("Unknown request status: " + status);
+            }
+        } else if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_EMULATED_STATE) {
+            switch (status) {
+                case STATUS_ACTIVE:
+                    mActiveOverride = Optional.of(request);
+                    break;
+                case STATUS_CANCELED:
+                    if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
+                        mActiveOverride = Optional.empty();
+                    }
+                    break;
+                case STATUS_UNKNOWN: // same as default
+                default:
+                    throw new IllegalArgumentException("Unknown request status: " + status);
             }
         } else {
-            throw new IllegalArgumentException("Unknown request status: " + status);
+            throw new IllegalArgumentException(
+                        "Unknown OverrideRest type: " + request.getRequestType());
         }
 
         boolean updatedPendingState = updatePendingStateLocked();
@@ -564,6 +617,18 @@
         mHandler.post(this::notifyPolicyIfNeeded);
     }
 
+    /**
+     * Sets the new base state of the device and notifies the process that made the base state
+     * override request that the request is now active.
+     */
+    @GuardedBy("mLock")
+    private void enableBaseStateRequestLocked(OverrideRequest request) {
+        setBaseState(request.getRequestedState());
+        mActiveBaseStateOverride = Optional.of(request);
+        ProcessRecord processRecord = mProcessRecords.get(request.getPid());
+        processRecord.notifyRequestActiveAsync(request.getToken());
+    }
+
     private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
         synchronized (mLock) {
             if (mProcessRecords.contains(pid)) {
@@ -606,7 +671,8 @@
                         + " has no registered callback.");
             }
 
-            if (mOverrideRequestController.hasRequest(token)) {
+            if (mOverrideRequestController.hasRequest(token,
+                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE)) {
                 throw new IllegalStateException("Request has already been made for the supplied"
                         + " token: " + token);
             }
@@ -617,7 +683,8 @@
                         + " is not supported.");
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags);
+            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
             mOverrideRequestController.addRequest(request);
         }
     }
@@ -633,6 +700,44 @@
         }
     }
 
+    private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
+            @NonNull IBinder token) {
+        synchronized (mLock) {
+            final Optional<DeviceState> deviceState = getStateLocked(state);
+            if (!deviceState.isPresent()) {
+                throw new IllegalArgumentException("Requested state: " + state
+                        + " is not supported.");
+            }
+
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+
+            if (mOverrideRequestController.hasRequest(token,
+                    OVERRIDE_REQUEST_TYPE_BASE_STATE)) {
+                throw new IllegalStateException("Request has already been made for the supplied"
+                        + " token: " + token);
+            }
+
+            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+                    OVERRIDE_REQUEST_TYPE_BASE_STATE);
+            mOverrideRequestController.addBaseStateRequest(request);
+        }
+    }
+
+    private void cancelBaseStateOverrideInternal(int callingPid) {
+        synchronized (mLock) {
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+            setBaseState(mDeviceStateProviderListener.mCurrentBaseState);
+        }
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
 
@@ -729,6 +834,8 @@
     }
 
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
+        @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
+
         @Override
         public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
             if (newDeviceStates.length == 0) {
@@ -743,7 +850,7 @@
             if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
                 throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
-
+            mCurrentBaseState = identifier;
             setBaseState(identifier);
         }
     }
@@ -895,6 +1002,38 @@
         }
 
         @Override // Binder call
+        public void requestBaseStateOverride(IBinder token, int state, int flags) {
+            final int callingPid = Binder.getCallingPid();
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to control base state of device.");
+
+            if (token == null) {
+                throw new IllegalArgumentException("Request token must not be null.");
+            }
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                requestBaseStateOverrideInternal(state, flags, callingPid, token);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
+        public void cancelBaseStateOverride() {
+            final int callingPid = Binder.getCallingPid();
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to control base state of device.");
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                cancelBaseStateOverrideInternal(callingPid);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver result) {
             new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 659ee75..8c6068d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -16,11 +16,8 @@
 
 package com.android.server.devicestate;
 
-import static android.Manifest.permission.CONTROL_DEVICE_STATE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
@@ -39,6 +36,8 @@
 public class DeviceStateManagerShellCommand extends ShellCommand {
     @Nullable
     private static DeviceStateRequest sLastRequest;
+    @Nullable
+    private static DeviceStateRequest sLastBaseStateRequest;
 
     private final DeviceStateManagerService mService;
     private final DeviceStateManager mClient;
@@ -58,6 +57,8 @@
         switch (cmd) {
             case "state":
                 return runState(pw);
+            case "base-state":
+                return runBaseState(pw);
             case "print-state":
                 return runPrintState(pw);
             case "print-states":
@@ -89,10 +90,6 @@
             return 0;
         }
 
-        final Context context = mService.getContext();
-        context.enforceCallingOrSelfPermission(
-                CONTROL_DEVICE_STATE,
-                "Permission required to request device state.");
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
             if ("reset".equals(nextArg)) {
@@ -127,6 +124,47 @@
         return 0;
     }
 
+    private int runBaseState(PrintWriter pw) {
+        final String nextArg = getNextArg();
+        if (nextArg == null) {
+            printAllStates(pw);
+            return 0;
+        }
+
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if ("reset".equals(nextArg)) {
+                if (sLastBaseStateRequest != null) {
+                    mClient.cancelBaseStateOverride();
+                    sLastBaseStateRequest = null;
+                }
+            } else {
+                int requestedState = Integer.parseInt(nextArg);
+                DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
+
+                mClient.requestBaseStateOverride(request, null, null);
+
+                sLastBaseStateRequest = request;
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: requested state should be an integer");
+            return -1;
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println("Error: " + e.getMessage());
+            getErrPrintWriter().println("-------------------");
+            getErrPrintWriter().println("Run:");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("    print-states");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("to get the list of currently supported device states");
+            return -1;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
+        return 0;
+    }
+
     private int runPrintState(PrintWriter pw) {
         Optional<DeviceState> deviceState = mService.getCommittedState();
         if (deviceState.isPresent()) {
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 35a4c84..325e384 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -16,9 +16,13 @@
 
 package com.android.server.devicestate;
 
+import android.annotation.IntDef;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A request to override the state managed by {@link DeviceStateManagerService}.
  *
@@ -30,13 +34,47 @@
     private final int mRequestedState;
     @DeviceStateRequest.RequestFlags
     private final int mFlags;
+    @OverrideRequestType
+    private final int mRequestType;
+
+    /**
+     * Denotes that the request is meant to override the emulated state of the device. This will
+     * not change the base (physical) state of the device.
+     *
+     * This request type should be used if you are looking to emulate a device state for a feature
+     * but want the system to be aware of the physical state of the device.
+     */
+    public static final int OVERRIDE_REQUEST_TYPE_EMULATED_STATE = 0;
+
+    /**
+     * Denotes that the request is meant to override the base (physical) state of the device.
+     * Overriding the base state may not change the emulated state of the device if there is also an
+     * override request active for that property.
+     *
+     * This request type should only be used for testing, where you want to simulate the physical
+     * state of the device changing.
+     */
+    public static final int OVERRIDE_REQUEST_TYPE_BASE_STATE = 1;
+
+    /**
+     * Flags for signifying the type of {@link OverrideRequest}.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = {
+            OVERRIDE_REQUEST_TYPE_BASE_STATE,
+            OVERRIDE_REQUEST_TYPE_EMULATED_STATE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OverrideRequestType {}
 
     OverrideRequest(IBinder token, int pid, int requestedState,
-            @DeviceStateRequest.RequestFlags int flags) {
+            @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
         mRequestedState = requestedState;
         mFlags = flags;
+        mRequestType = requestType;
     }
 
     IBinder getToken() {
@@ -55,4 +93,9 @@
     int getFlags() {
         return mFlags;
     }
+
+    @OverrideRequestType
+    int getRequestType() {
+        return mRequestType;
+    }
 }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 01f5a09..e39c732 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -75,6 +75,8 @@
 
     // Handle to the current override request, null if none.
     private OverrideRequest mRequest;
+    // Handle to the current base state override request, null if none.
+    private OverrideRequest mBaseStateRequest;
 
     private boolean mStickyRequestsAllowed;
     // The current request has outlived their process.
@@ -111,13 +113,23 @@
         }
     }
 
+    void addBaseStateRequest(@NonNull OverrideRequest request) {
+        OverrideRequest previousRequest = mBaseStateRequest;
+        mBaseStateRequest = request;
+        mListener.onStatusChanged(request, STATUS_ACTIVE);
+
+        if (previousRequest != null) {
+            cancelRequestLocked(previousRequest);
+        }
+    }
+
     /**
      * Cancels the request with the specified {@code token} and notifies the listener of all changes
      * to request status as a result of this operation.
      */
     void cancelRequest(@NonNull OverrideRequest request) {
         // Either don't have a current request or attempting to cancel an already cancelled request
-        if (!hasRequest(request.getToken())) {
+        if (!hasRequest(request.getToken(), request.getRequestType())) {
             return;
         }
         cancelCurrentRequestLocked();
@@ -144,11 +156,24 @@
     }
 
     /**
+     * Cancels the current base state override request, this could be due to the physical
+     * configuration of the device changing.
+     */
+    void cancelBaseStateOverrideRequest() {
+        cancelCurrentBaseStateRequestLocked();
+    }
+
+    /**
      * Returns {@code true} if this controller is current managing a request with the specified
      * {@code token}, {@code false} otherwise.
      */
-    boolean hasRequest(@NonNull IBinder token) {
-        return mRequest != null && token == mRequest.getToken();
+    boolean hasRequest(@NonNull IBinder token,
+            @OverrideRequest.OverrideRequestType int requestType) {
+        if (requestType == OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+            return mBaseStateRequest != null && token == mBaseStateRequest.getToken();
+        } else {
+            return mRequest != null && token == mRequest.getToken();
+        }
     }
 
     /**
@@ -157,11 +182,11 @@
      * operation.
      */
     void handleProcessDied(int pid) {
-        if (mRequest == null) {
-            return;
+        if (mBaseStateRequest != null && mBaseStateRequest.getPid() == pid) {
+            cancelCurrentBaseStateRequestLocked();
         }
 
-        if (mRequest.getPid() == pid) {
+        if (mRequest != null && mRequest.getPid() == pid) {
             if (mStickyRequestsAllowed) {
                 // Do not cancel the requests now because sticky requests are allowed. These
                 // requests will be cancelled on a call to cancelStickyRequests().
@@ -176,7 +201,10 @@
      * Notifies the controller that the base state has changed. The controller will notify the
      * listener of all changes to request status as a result of this change.
      */
-    void handleBaseStateChanged() {
+    void handleBaseStateChanged(int state) {
+        if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) {
+            cancelBaseStateOverrideRequest();
+        }
         if (mRequest == null) {
             return;
         }
@@ -192,11 +220,12 @@
      * notify the listener of all changes to request status as a result of this change.
      */
     void handleNewSupportedStates(int[] newSupportedStates) {
-        if (mRequest == null) {
-            return;
+        if (mBaseStateRequest != null && !contains(newSupportedStates,
+                mBaseStateRequest.getRequestedState())) {
+            cancelCurrentBaseStateRequestLocked();
         }
 
-        if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+        if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
             cancelCurrentRequestLocked();
         }
     }
@@ -228,10 +257,23 @@
             return;
         }
         mStickyRequest = false;
-        mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+        cancelRequestLocked(mRequest);
         mRequest = null;
     }
 
+    /**
+     * Handles cancelling {@code mBaseStateRequest}.
+     * Notifies the listener of the canceled status as well.
+     */
+    private void cancelCurrentBaseStateRequestLocked() {
+        if (mBaseStateRequest == null) {
+            Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+            return;
+        }
+        cancelRequestLocked(mBaseStateRequest);
+        mBaseStateRequest = null;
+    }
+
     private static boolean contains(int[] array, int value) {
         for (int i = 0; i < array.length; i++) {
             if (array[i] == value) {
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
index d74c702..7c9a484 100644
--- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -27,6 +27,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -287,6 +288,14 @@
                     localDate)) {
                 return lastBrightnessStats;
             } else {
+                // It is a new day, and we have available data, so log data. The daily boundary
+                // might not be right if the user changes timezones but that is fine, since it
+                // won't be that frequent.
+                if (lastBrightnessStats != null) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.AMBIENT_BRIGHTNESS_STATS_REPORTED,
+                            lastBrightnessStats.getStats(),
+                            lastBrightnessStats.getBucketBoundaries());
+                }
                 AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate,
                         BUCKET_BOUNDARIES_FOR_NEW_STATS);
                 if (userStats.size() == MAX_DAYS_TO_TRACK) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b3aee22..7b60421 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -593,10 +593,14 @@
         }
 
         pw.println();
+        pw.println("  mAmbientBrightnessThresholds=");
         mAmbientBrightnessThresholds.dump(pw);
+        pw.println("  mScreenBrightnessThresholds=");
         mScreenBrightnessThresholds.dump(pw);
+        pw.println("  mScreenBrightnessThresholdsIdle=");
         mScreenBrightnessThresholdsIdle.dump(pw);
-        mScreenBrightnessThresholdsIdle.dump(pw);
+        pw.println("  mAmbientBrightnessThresholdsIdle=");
+        mAmbientBrightnessThresholdsIdle.dump(pw);
     }
 
     private String configStateToString(int state) {
@@ -861,6 +865,7 @@
                 Slog.d(TAG, "updateAmbientLux: "
                         + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
                         + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+                        + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
                         + "mAmbientLux=" + mAmbientLux);
             }
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index e9640cf..a060f07 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.os.IBinder;
 
 import java.util.Objects;
@@ -26,6 +29,7 @@
 public class DisplayControl {
     private static native IBinder nativeCreateDisplay(String name, boolean secure);
     private static native void nativeDestroyDisplay(IBinder displayToken);
+    private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
     /**
      * Create a display in SurfaceFlinger.
@@ -52,4 +56,11 @@
         nativeDestroyDisplay(displayToken);
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
+        nativeOverrideHdrTypes(displayToken, modes);
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4165186..81219ba 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -27,6 +27,7 @@
 import android.os.PowerManager;
 import android.text.TextUtils;
 import android.util.MathUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.Spline;
 import android.view.DisplayAddress;
@@ -51,7 +52,7 @@
 import com.android.server.display.config.SensorDetails;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.Thresholds;
+import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.XmlParser;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -188,42 +189,153 @@
  *      <ambientLightHorizonLong>10001</ambientLightHorizonLong>
  *      <ambientLightHorizonShort>2001</ambientLightHorizonShort>
  *
- *      <displayBrightnessChangeThresholds> // Thresholds for screen changes
- *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
- *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.002</minimum>  // Minimum change needed in screen brightness to darken.
- *        </darkeningThresholds>
- *      </displayBrightnessChangeThresholds>
- *
- *      <ambientBrightnessChangeThresholds> // Thresholds for lux changes
- *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
- *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.004</minimum>  // Minimum change needed in ambient brightness to darken.
- *        </darkeningThresholds>
- *      </ambientBrightnessChangeThresholds>
- *
- *      <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
- *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
- *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.002</minimum>  // Minimum change needed in screen brightness to darken.
- *        </darkeningThresholds>
- *      </displayBrightnessChangeThresholdsIdle>
- *
- *      <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
- *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
- *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.004</minimum>  // Minimum change needed in ambient brightness to darken.
- *        </darkeningThresholds>
- *      </ambientBrightnessChangeThresholdsIdle>
- *
+ *     <ambientBrightnessChangeThresholds>  // Thresholds for lux changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in ambient brightness to brighten screen.
+ *             <minimum>10</minimum>
+ *             // Percentage increase of lux needed to increase the screen brightness at a lux range
+ *             // above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>13</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>100</threshold><percentage>14</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>200</threshold><percentage>15</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in ambient brightness to darken screen.
+ *             <minimum>30</minimum>
+ *             // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ *             // above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>15</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>300</threshold><percentage>16</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>400</threshold><percentage>17</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </ambientBrightnessChangeThresholds>
+ *     <displayBrightnessChangeThresholds>   // Thresholds for screen brightness changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in screen brightness to brighten screen.
+ *             <minimum>0.1</minimum>
+ *             // Percentage increase of screen brightness needed to increase the screen brightness
+ *             // at a lux range above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold>
+ *                     <percentage>9</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.10</threshold>
+ *                     <percentage>10</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.20</threshold>
+ *                     <percentage>11</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in screen brightness to darken screen.
+ *             <minimum>0.3</minimum>
+ *             // Percentage increase of screen brightness needed to decrease the screen brightness
+ *             // at a lux range above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>11</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.11</threshold><percentage>12</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.21</threshold><percentage>13</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </displayBrightnessChangeThresholds>
+ *     <ambientBrightnessChangeThresholdsIdle>   // Thresholds for lux changes in idle mode
+ *         <brighteningThresholds>
+ *             // Minimum change needed in ambient brightness to brighten screen in idle mode
+ *             <minimum>20</minimum>
+ *             // Percentage increase of lux needed to increase the screen brightness at a lux range
+ *             // above the specified threshold whilst in idle mode.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>21</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>500</threshold><percentage>22</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>600</threshold><percentage>23</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in ambient brightness to darken screen in idle mode
+ *             <minimum>40</minimum>
+ *             // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ *             // above the specified threshold whilst in idle mode.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>23</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>700</threshold><percentage>24</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>800</threshold><percentage>25</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </ambientBrightnessChangeThresholdsIdle>
+ *     <displayBrightnessChangeThresholdsIdle>    // Thresholds for idle screen brightness changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in screen brightness to brighten screen in idle mode
+ *             <minimum>0.2</minimum>
+ *             // Percentage increase of screen brightness needed to increase the screen brightness
+ *             // at a lux range above the specified threshold whilst in idle mode
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>17</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.12</threshold><percentage>18</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.22</threshold><percentage>19</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in screen brightness to darken screen in idle mode
+ *             <minimum>0.4</minimum>
+ *             // Percentage increase of screen brightness needed to decrease the screen brightness
+ *             // at a lux range above the specified threshold whilst in idle mode
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>19</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.13</threshold><percentage>20</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.23</threshold><percentage>21</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </displayBrightnessChangeThresholdsIdle>
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -247,6 +359,13 @@
     private static final String NO_SUFFIX_FORMAT = "%d";
     private static final long STABLE_FLAG = 1L << 62;
 
+    private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+    private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
     private static final int INTERPOLATION_DEFAULT = 0;
     private static final int INTERPOLATION_LINEAR = 1;
 
@@ -344,6 +463,31 @@
     private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
     private float mAmbientLuxDarkeningMinThreshold = 0.0f;
     private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
+
+    // Screen brightness thresholds levels & percentages
+    private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+    private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+    // Screen brightness thresholds levels & percentages for idle mode
+    private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+    private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+    // Ambient brightness thresholds levels & percentages
+    private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+    private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+    // Ambient brightness thresholds levels & percentages for idle mode
+    private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+    private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
     private Spline mBrightnessToBacklightSpline;
     private Spline mBacklightToBrightnessSpline;
     private Spline mBacklightToNitsSpline;
@@ -684,7 +828,7 @@
     /**
      * The minimum value for the ambient lux increase for a screen brightness change to actually
      * occur.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxBrighteningMinThreshold() {
         return mAmbientLuxBrighteningMinThreshold;
@@ -693,7 +837,7 @@
     /**
      * The minimum value for the ambient lux decrease for a screen brightness change to actually
      * occur.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxDarkeningMinThreshold() {
         return mAmbientLuxDarkeningMinThreshold;
@@ -702,7 +846,7 @@
     /**
      * The minimum value for the ambient lux increase for a screen brightness change to actually
      * occur while in idle screen brightness mode.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxBrighteningMinThresholdIdle() {
         return mAmbientLuxBrighteningMinThresholdIdle;
@@ -711,12 +855,262 @@
     /**
      * The minimum value for the ambient lux decrease for a screen brightness change to actually
      * occur while in idle screen brightness mode.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxDarkeningMinThresholdIdle() {
         return mAmbientLuxDarkeningMinThresholdIdle;
     }
 
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenBrighteningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentages[n]
+     * level[MAX] <= value             = mScreenBrighteningPercentages[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenBrighteningPercentages applies
+     */
+    public float[] getScreenBrighteningLevels() {
+        return mScreenBrighteningLevels;
+    }
+
+    /**
+     * The array that describes the screen brightening threshold percentage change at each screen
+     * brightness level described in mScreenBrighteningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change
+     */
+    public float[] getScreenBrighteningPercentages() {
+        return mScreenBrighteningPercentages;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenDarkeningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentages[n]
+     * level[MAX] <= value             = mScreenDarkeningPercentages[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenDarkeningPercentages applies
+     */
+    public float[] getScreenDarkeningLevels() {
+        return mScreenDarkeningLevels;
+    }
+
+    /**
+     * The array that describes the screen darkening threshold percentage change at each screen
+     * brightness level described in mScreenDarkeningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change
+     */
+    public float[] getScreenDarkeningPercentages() {
+        return mScreenDarkeningPercentages;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold
+     * percentage applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientBrighteningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentages[n]
+     * level[MAX] <= value             = mAmbientBrighteningPercentages[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientBrighteningPercentages applies
+     */
+    public float[] getAmbientBrighteningLevels() {
+        return mAmbientBrighteningLevels;
+    }
+
+    /**
+     * The array that describes the ambient brightening threshold percentage change at each ambient
+     * brightness level described in mAmbientBrighteningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change
+     */
+    public float[] getAmbientBrighteningPercentages() {
+        return mAmbientBrighteningPercentages;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientDarkeningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentages[n]
+     * level[MAX] <= value             = mAmbientDarkeningPercentages[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientDarkeningPercentages applies
+     */
+    public float[] getAmbientDarkeningLevels() {
+        return mAmbientDarkeningLevels;
+    }
+
+    /**
+     * The array that describes the ambient darkening threshold percentage change at each ambient
+     * brightness level described in mAmbientDarkeningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change
+     */
+    public float[] getAmbientDarkeningPercentages() {
+        return mAmbientDarkeningPercentages;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenBrighteningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentagesIdle[n]
+     * level[MAX] <= value             = mScreenBrighteningPercentagesIdle[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenBrighteningPercentagesIdle applies
+     */
+    public float[] getScreenBrighteningLevelsIdle() {
+        return mScreenBrighteningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the screen brightening threshold percentage change at each screen
+     * brightness level described in mScreenBrighteningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change while in idle mode.
+     */
+    public float[] getScreenBrighteningPercentagesIdle() {
+        return mScreenBrighteningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenDarkeningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentagesIdle[n]
+     * level[MAX] <= value             = mScreenDarkeningPercentagesIdle[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenDarkeningPercentagesIdle applies
+     */
+    public float[] getScreenDarkeningLevelsIdle() {
+        return mScreenDarkeningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the screen darkening threshold percentage change at each screen
+     * brightness level described in mScreenDarkeningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change while in idle mode.
+     */
+    public float[] getScreenDarkeningPercentagesIdle() {
+        return mScreenDarkeningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientBrighteningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentagesIdle[n]
+     * level[MAX] <= value             = mAmbientBrighteningPercentagesIdle[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientBrighteningPercentagesIdle applies
+     */
+    public float[] getAmbientBrighteningLevelsIdle() {
+        return mAmbientBrighteningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the ambient brightness threshold percentage change whilst in
+     * idle screen brightness mode at each ambient brightness level described in
+     * mAmbientBrighteningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of ambient brightness increase required in order
+     * for the screen brightness to change
+     */
+    public float[] getAmbientBrighteningPercentagesIdle() {
+        return mAmbientBrighteningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientDarkeningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentagesIdle[n]
+     * level[MAX] <= value             = mAmbientDarkeningPercentagesIdle[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientDarkeningPercentagesIdle applies
+     */
+    public float[] getAmbientDarkeningLevelsIdle() {
+        return mAmbientDarkeningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the ambient brightness threshold percentage change whilst in
+     * idle screen brightness mode at each ambient brightness level described in
+     * mAmbientDarkeningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of ambient brightness decrease required in order
+     * for the screen brightness to change
+     */
+    public float[] getAmbientDarkeningPercentagesIdle() {
+        return mAmbientDarkeningPercentagesIdle;
+    }
+
     SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
@@ -812,14 +1206,17 @@
                 + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
                 + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
                 + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+                + "\n"
                 + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
                 + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
                 + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
                 + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
                 + ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
                 + ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
+                + "\n"
                 + ", mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
+                + "\n"
                 + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
                 + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
                 + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
@@ -829,6 +1226,41 @@
                 + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
                 + ", mAmbientLuxBrighteningMinThresholdIdle="
                 + mAmbientLuxBrighteningMinThresholdIdle
+                + "\n"
+                + ", mScreenBrighteningLevels=" + Arrays.toString(
+                mScreenBrighteningLevels)
+                + ", mScreenBrighteningPercentages=" + Arrays.toString(
+                mScreenBrighteningPercentages)
+                + ", mScreenDarkeningLevels=" + Arrays.toString(
+                mScreenDarkeningLevels)
+                + ", mScreenDarkeningPercentages=" + Arrays.toString(
+                mScreenDarkeningPercentages)
+                + ", mAmbientBrighteningLevels=" + Arrays.toString(
+                mAmbientBrighteningLevels)
+                + ", mAmbientBrighteningPercentages=" + Arrays.toString(
+                mAmbientBrighteningPercentages)
+                + ", mAmbientDarkeningLevels=" + Arrays.toString(
+                mAmbientDarkeningLevels)
+                + ", mAmbientDarkeningPercentages=" + Arrays.toString(
+                mAmbientDarkeningPercentages)
+                + "\n"
+                + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+                mAmbientBrighteningLevelsIdle)
+                + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
+                mAmbientBrighteningPercentagesIdle)
+                + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
+                mAmbientDarkeningLevelsIdle)
+                + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
+                mAmbientDarkeningPercentagesIdle)
+                + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
+                mScreenBrighteningLevelsIdle)
+                + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
+                mScreenBrighteningPercentagesIdle)
+                + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
+                mScreenDarkeningLevelsIdle)
+                + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
+                mScreenDarkeningPercentagesIdle)
+                + "\n"
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -914,6 +1346,7 @@
         loadBrightnessMapFromConfigXml();
         loadBrightnessRampsFromConfigXml();
         loadAmbientLightSensorFromConfigXml();
+        loadBrightnessChangeThresholdsFromXml();
         setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
         mLoadedFrom = "<config.xml>";
@@ -1454,91 +1887,286 @@
         }
     }
 
+    private void loadBrightnessChangeThresholdsFromXml() {
+        loadBrightnessChangeThresholds(/* config= */ null);
+    }
+
     private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
-        Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
-        Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
-        Thresholds displayBrightnessThresholdsIdle =
-                config.getDisplayBrightnessChangeThresholdsIdle();
-        Thresholds ambientBrightnessThresholdsIdle =
-                config.getAmbientBrightnessChangeThresholdsIdle();
-
-        loadDisplayBrightnessThresholds(displayBrightnessThresholds);
-        loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
-        loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
-        loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+        loadDisplayBrightnessThresholds(config);
+        loadAmbientBrightnessThresholds(config);
+        loadDisplayBrightnessThresholdsIdle(config);
+        loadAmbientBrightnessThresholdsIdle(config);
     }
 
-    private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
-        if (displayBrightnessThresholds != null) {
-            BrightnessThresholds brighteningScreen =
-                    displayBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningScreen =
-                    displayBrightnessThresholds.getDarkeningThresholds();
+    private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
+        BrightnessThresholds brighteningScreen = null;
+        BrightnessThresholds darkeningScreen = null;
+        if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
+            brighteningScreen =
+                    config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
+            darkeningScreen =
+                    config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
 
-            if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
-                mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
-            }
-            if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
-                mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
-            }
+        }
+
+        // Screen bright/darkening threshold levels for active mode
+        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningScreen,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+
+        mScreenBrighteningLevels = screenBrighteningPair.first;
+        mScreenBrighteningPercentages = screenBrighteningPair.second;
+
+        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningScreen,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenDarkeningLevels = screenDarkeningPair.first;
+        mScreenDarkeningPercentages = screenDarkeningPair.second;
+
+        // Screen bright/darkening threshold minimums for active mode
+        if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+            mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
+        }
+        if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+            mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
         }
     }
 
-    private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
-        if (ambientBrightnessThresholds != null) {
-            BrightnessThresholds brighteningAmbientLux =
-                    ambientBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningAmbientLux =
-                    ambientBrightnessThresholds.getDarkeningThresholds();
+    private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
+        // Ambient Brightness Threshold Levels
+        BrightnessThresholds brighteningAmbientLux = null;
+        BrightnessThresholds darkeningAmbientLux = null;
+        if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
+            brighteningAmbientLux =
+                    config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
+            darkeningAmbientLux =
+                    config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
+        }
 
-            final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
-            final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+        // Ambient bright/darkening threshold levels for active mode
+        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningAmbientLux,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+        mAmbientBrighteningLevels = ambientBrighteningPair.first;
+        mAmbientBrighteningPercentages = ambientBrighteningPair.second;
 
-            if (ambientBrighteningThreshold != null) {
-                mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
-            }
-            if (ambientDarkeningThreshold != null) {
-                mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
-            }
+        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningAmbientLux,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+        mAmbientDarkeningLevels = ambientDarkeningPair.first;
+        mAmbientDarkeningPercentages = ambientDarkeningPair.second;
+
+        // Ambient bright/darkening threshold minimums for active/idle mode
+        if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
+            mAmbientLuxBrighteningMinThreshold =
+                    brighteningAmbientLux.getMinimum().floatValue();
+        }
+
+        if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
+            mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
         }
     }
 
-    private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
-        if (idleDisplayBrightnessThresholds != null) {
-            BrightnessThresholds brighteningScreenIdle =
-                    idleDisplayBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningScreenIdle =
-                    idleDisplayBrightnessThresholds.getDarkeningThresholds();
+    private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
+        BrightnessThresholds brighteningScreenIdle = null;
+        BrightnessThresholds darkeningScreenIdle = null;
+        if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
+            brighteningScreenIdle =
+                    config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+            darkeningScreenIdle =
+                    config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+        }
 
-            if (brighteningScreenIdle != null
-                    && brighteningScreenIdle.getMinimum() != null) {
-                mScreenBrighteningMinThresholdIdle =
-                        brighteningScreenIdle.getMinimum().floatValue();
-            }
-            if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
-                mScreenDarkeningMinThresholdIdle =
-                        darkeningScreenIdle.getMinimum().floatValue();
-            }
+        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningScreenIdle,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
+        mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
+
+        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningScreenIdle,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
+        mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
+
+        if (brighteningScreenIdle != null
+                && brighteningScreenIdle.getMinimum() != null) {
+            mScreenBrighteningMinThresholdIdle =
+                    brighteningScreenIdle.getMinimum().floatValue();
+        }
+        if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+            mScreenDarkeningMinThresholdIdle =
+                    darkeningScreenIdle.getMinimum().floatValue();
         }
     }
 
-    private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
-        if (idleAmbientBrightnessThresholds != null) {
-            BrightnessThresholds brighteningAmbientLuxIdle =
-                    idleAmbientBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningAmbientLuxIdle =
-                    idleAmbientBrightnessThresholds.getDarkeningThresholds();
+    private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
+        BrightnessThresholds brighteningAmbientLuxIdle = null;
+        BrightnessThresholds darkeningAmbientLuxIdle = null;
+        if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
+            brighteningAmbientLuxIdle =
+                    config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+            darkeningAmbientLuxIdle =
+                    config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+        }
 
-            if (brighteningAmbientLuxIdle != null
-                    && brighteningAmbientLuxIdle.getMinimum() != null) {
-                mAmbientLuxBrighteningMinThresholdIdle =
-                        brighteningAmbientLuxIdle.getMinimum().floatValue();
+        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningAmbientLuxIdle,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+        mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
+        mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
+
+        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningAmbientLuxIdle,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+        mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
+        mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
+
+        if (brighteningAmbientLuxIdle != null
+                && brighteningAmbientLuxIdle.getMinimum() != null) {
+            mAmbientLuxBrighteningMinThresholdIdle =
+                    brighteningAmbientLuxIdle.getMinimum().floatValue();
+        }
+
+        if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+            mAmbientLuxDarkeningMinThresholdIdle =
+                    darkeningAmbientLuxIdle.getMinimum().floatValue();
+        }
+    }
+
+    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+            int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
+            float[] defaultPercentage) {
+        return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
+                configFallbackPercentage, defaultLevels, defaultPercentage, false);
+    }
+
+    // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+    // percentages for brightness levels at or above the lux value.
+    // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+    // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+    // rest of the framework. Values were also defined in different units (permille vs percent).
+    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+            int configFallbackThreshold, int configFallbackPermille,
+            float[] defaultLevels, float[] defaultPercentage,
+            boolean potentialOldBrightnessScale) {
+        if (thresholds != null
+                && thresholds.getBrightnessThresholdPoints() != null
+                && thresholds.getBrightnessThresholdPoints()
+                        .getBrightnessThresholdPoint().size() != 0) {
+
+            // The level and percentages arrays are equal length in the ddc (new system)
+            List<ThresholdPoint> points =
+                    thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+            final int size = points.size();
+
+            float[] thresholdLevels = new float[size];
+            float[] thresholdPercentages = new float[size];
+
+            int i = 0;
+            for (ThresholdPoint point : points) {
+                thresholdLevels[i] = point.getThreshold().floatValue();
+                thresholdPercentages[i] = point.getPercentage().floatValue();
+                i++;
             }
-            if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
-                mAmbientLuxDarkeningMinThresholdIdle =
-                        darkeningAmbientLuxIdle.getMinimum().floatValue();
+            return new Pair<>(thresholdLevels, thresholdPercentages);
+        } else {
+            // The level and percentages arrays are unequal length in config.xml (old system)
+            // We prefix the array with a 0 value to ensure they can be handled consistently
+            // with the new system.
+
+            // Load levels array
+            int[] configThresholdArray = mContext.getResources().getIntArray(
+                    configFallbackThreshold);
+            int configThresholdsSize;
+            if (configThresholdArray == null || configThresholdArray.length == 0) {
+                configThresholdsSize = 1;
+            } else {
+                configThresholdsSize = configThresholdArray.length + 1;
+            }
+
+
+            // Load percentage array
+            int[] configPermille = mContext.getResources().getIntArray(
+                    configFallbackPermille);
+
+            // Ensure lengths match up
+            boolean emptyArray = configPermille == null || configPermille.length == 0;
+            if (emptyArray && configThresholdsSize == 1) {
+                return new Pair<>(defaultLevels, defaultPercentage);
+            }
+            if (emptyArray || configPermille.length != configThresholdsSize) {
+                throw new IllegalArgumentException(
+                        "Brightness threshold arrays do not align in length");
+            }
+
+            // Calculate levels array
+            float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+            // Start at 1, so that 0 index value is 0.0f (default)
+            for (int i = 1; i < configThresholdsSize; i++) {
+                configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+            }
+            if (potentialOldBrightnessScale) {
+                configThresholdWithZeroPrefixed =
+                        constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+            }
+
+            // Calculate percentages array
+            float[] configPercentage = new float[configThresholdsSize];
+            for (int i = 0; i < configPermille.length; i++) {
+                configPercentage[i] = configPermille[i] / 10.0f;
+            }            return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+        }
+    }
+
+    /**
+     * This check is due to historical reasons, where screen thresholdLevels used to be
+     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+     * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+     * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+     */
+    private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+        if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+                /* maxValueInclusive= */ 1.0f)) {
+            return thresholdLevels;
+        }
+
+        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+        for (int index = 0; thresholdLevels.length > index; ++index) {
+            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+        }
+        return thresholdLevelsScaled;
+    }
+
+    private boolean isAllInRange(float[] configArray, float minValueInclusive,
+            float maxValueInclusive) {
+        for (float v : configArray) {
+            if (v < minValueInclusive || v > maxValueInclusive) {
+                return false;
             }
         }
+        return true;
     }
 
     private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 4ac7990..33a63a9 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -167,7 +167,8 @@
                 return;
             }
             if (DEBUG) {
-                Trace.beginSection("handleDisplayDeviceChanged");
+                Trace.traceBegin(Trace.TRACE_TAG_POWER,
+                        "handleDisplayDeviceChanged");
             }
             int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
             if (diff == DisplayDeviceInfo.DIFF_STATE) {
@@ -189,7 +190,7 @@
             device.applyPendingDisplayDeviceInfoChangesLocked();
             sendEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             if (DEBUG) {
-                Trace.endSection();
+                Trace.traceEnd(Trace.TRACE_TAG_POWER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9ab78bc..84891c7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -80,7 +80,6 @@
 import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.display.WifiDisplayStatus;
 import android.hardware.graphics.common.DisplayDecorationSupport;
-import android.hardware.input.InputManagerInternal;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
 import android.net.Uri;
@@ -134,6 +133,7 @@
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.utils.SensorUtils;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -775,18 +775,24 @@
                 return; // Display no longer exists or no change.
             }
 
-            traceMessage = "requestDisplayStateInternal("
-                    + displayId + ", "
-                    + Display.stateToString(state)
-                    + ", brightness=" + brightnessState
-                    + ", sdrBrightness=" + sdrBrightnessState + ")";
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, traceMessage, displayId);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                traceMessage = Display.stateToString(state)
+                           + ", brightness=" + brightnessState
+                           + ", sdrBrightness=" + sdrBrightnessState;
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_POWER,
+                        "requestDisplayStateInternal:" + displayId,
+                        traceMessage, displayId);
+            }
 
             mDisplayStates.setValueAt(index, state);
             brightnessPair.brightness = brightnessState;
             brightnessPair.sdrBrightness = sdrBrightnessState;
             runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId)
                     .getPrimaryDisplayDeviceLocked());
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+                        "requestDisplayStateInternal:" + displayId, displayId);
+            }
         }
 
         // Setting the display power state can take hundreds of milliseconds
@@ -796,7 +802,6 @@
         if (runnable != null) {
             runnable.run();
         }
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, traceMessage, displayId);
     }
 
     private class SettingsObserver extends ContentObserver {
@@ -2562,10 +2567,10 @@
 
         final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
                 display, mSyncRoot);
-        final DisplayPowerController displayPowerController;
+        final DisplayPowerControllerInterface displayPowerController;
 
         if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) {
-            displayPowerController = new DisplayPowerController(
+            displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
                     () -> handleBrightnessChange(display));
@@ -3000,6 +3005,19 @@
             }
         }
 
+        @Override
+        public void overrideHdrTypes(int displayId, int[] modes) {
+            IBinder displayToken;
+            synchronized (mSyncRoot) {
+                displayToken = getDisplayToken(displayId);
+                if (displayToken == null) {
+                    throw new IllegalArgumentException("Invalid display: " + displayId);
+                }
+            }
+
+            DisplayControl.overrideHdrTypes(displayToken, modes);
+        }
+
         @Override // Binder call
         public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) {
             mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58a80e3..4752044 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -798,8 +798,9 @@
      * Notified when the display is changed. We use this to apply any changes that might be needed
      * when displays get swapped on foldable devices.  For example, different brightness properties
      * of each display need to be properly reflected in AutomaticBrightnessController.
+     *
+     * Make sure DisplayManagerService.mSyncRoot is held when this is called
      */
-    @GuardedBy("DisplayManagerService.mSyncRoot")
     @Override
     public void onDisplayChanged() {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
@@ -969,53 +970,77 @@
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
-            int[] ambientBrighteningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientBrighteningThresholds);
-            int[] ambientDarkeningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientDarkeningThresholds);
-            int[] ambientThresholdLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientThresholdLevels);
+            // Ambient Lux - Active Mode Brightness Thresholds
+            float[] ambientBrighteningThresholds =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+            float[] ambientDarkeningThresholds =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+            float[] ambientBrighteningLevels =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevels();
+            float[] ambientDarkeningLevels =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevels();
             float ambientDarkeningMinThreshold =
                     mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
             float ambientBrighteningMinThreshold =
                     mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
             HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
                     ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
                     ambientBrighteningMinThreshold);
 
-            int[] screenBrighteningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_screenBrighteningThresholds);
-            int[] screenDarkeningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_screenDarkeningThresholds);
-            int[] screenThresholdLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_screenThresholdLevels);
+            // Display - Active Mode Brightness Thresholds
+            float[] screenBrighteningThresholds =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentages();
+            float[] screenDarkeningThresholds =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentages();
+            float[] screenBrighteningLevels =
+                    mDisplayDeviceConfig.getScreenBrighteningLevels();
+            float[] screenDarkeningLevels =
+                    mDisplayDeviceConfig.getScreenDarkeningLevels();
             float screenDarkeningMinThreshold =
                     mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
             float screenBrighteningMinThreshold =
                     mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
             HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
-                    screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+                    screenBrighteningThresholds, screenDarkeningThresholds,
+                    screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+                    screenBrighteningMinThreshold, true);
 
-            // Idle screen thresholds
-            float screenDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
-            float screenBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
-            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
-                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
-            // Idle ambient thresholds
+            // Ambient Lux - Idle Screen Brightness Thresholds
             float ambientDarkeningMinThresholdIdle =
                     mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
             float ambientBrighteningMinThresholdIdle =
                     mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+            float[] ambientBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+            float[] ambientDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+            float[] ambientBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+            float[] ambientDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
             HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
-                    ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
-                    ambientBrighteningMinThresholdIdle);
+                    ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+                    ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+                    ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+            // Display - Idle Screen Brightness Thresholds
+            float screenDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+            float screenBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+            float[] screenBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+            float[] screenDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+            float[] screenBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+            float[] screenDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+                    screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+                    screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
 
             long brighteningLightDebounce = mDisplayDeviceConfig
                     .getAutoBrightnessBrighteningLightDebounce();
@@ -1171,13 +1196,10 @@
     }
 
     private void updatePowerState() {
-        if (DEBUG) {
-            Trace.beginSection("DisplayPowerController#updatePowerState");
-        }
+        Trace.traceBegin(Trace.TRACE_TAG_POWER,
+                "DisplayPowerController#updatePowerState");
         updatePowerStateInternal();
-        if (DEBUG) {
-            Trace.endSection();
-        }
+        Trace.traceEnd(Trace.TRACE_TAG_POWER);
     }
 
     private void updatePowerStateInternal() {
@@ -1597,9 +1619,9 @@
             // 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
-                    && ((mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
-                    || (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
-                    == 0)) {
+                    && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
+                    && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
+                    == 0) {
                 // We want to scale HDR brightness level with the SDR level
                 animateValue = mHbmController.getHdrBrightnessValue();
             }
@@ -1896,7 +1918,7 @@
                     }
                 },
                 () -> {
-                    sendUpdatePowerStateLocked();
+                    sendUpdatePowerState();
                     postBrightnessChangeRunnable();
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     if (mAutomaticBrightnessController != null) {
@@ -1912,7 +1934,7 @@
                 ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
         return new BrightnessThrottler(mHandler, data,
                 () -> {
-                    sendUpdatePowerStateLocked();
+                    sendUpdatePowerState();
                     postBrightnessChangeRunnable();
                 }, mUniqueDisplayId);
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
new file mode 100644
index 0000000..172b4be
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -0,0 +1,3002 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.metrics.LogMaker;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.MutableFloat;
+import android.util.MutableInt;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
+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.RingBuffer;
+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.brightness.BrightnessReason;
+import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.utils.SensorUtils;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the power state of the display.
+ *
+ * Handles the proximity sensor, light sensor, and animations between states
+ * including the screen off animation.
+ *
+ * This component acts independently of the rest of the power manager service.
+ * In particular, it does not share any state and it only communicates
+ * via asynchronous callbacks to inform the power manager that something has
+ * changed.
+ *
+ * Everything this class does internally is serialized on its handler although
+ * it may be accessed by other threads from the outside.
+ *
+ * Note that the power manager service guarantees that it will hold a suspend
+ * blocker as long as the display is not ready.  So most of the work done here
+ * does not need to worry about holding a suspend blocker unless it happens
+ * independently of the display ready signal.
+ *
+ * For debugging, you can make the color fade and brightness animations run
+ * slower by changing the "animator duration scale" option in Development Settings.
+ */
+final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks,
+        DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
+    private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
+    private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
+
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+
+    // If true, uses the color fade on animation.
+    // We might want to turn this off if we cannot get a guarantee that the screen
+    // actually turns on and starts showing new content after the call to set the
+    // screen state returns.  Playing the animation can also be somewhat slow.
+    private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
+
+    private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
+
+    private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
+    private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
+
+    private static final int MSG_UPDATE_POWER_STATE = 1;
+    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
+    private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
+    private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
+    private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
+    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
+    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
+    private static final int MSG_IGNORE_PROXIMITY = 8;
+    private static final int MSG_STOP = 9;
+    private static final int MSG_UPDATE_BRIGHTNESS = 10;
+    private static final int MSG_UPDATE_RBC = 11;
+    private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
+    private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
+
+    private static final int PROXIMITY_UNKNOWN = -1;
+    private static final int PROXIMITY_NEGATIVE = 0;
+    private static final int PROXIMITY_POSITIVE = 1;
+
+    // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
+    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+
+    private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
+
+    // Trigger proximity if distance is less than 5 cm.
+    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+    // State machine constants for tracking initial brightness ramp skipping when enabled.
+    private static final int RAMP_STATE_SKIP_NONE = 0;
+    private static final int RAMP_STATE_SKIP_INITIAL = 1;
+    private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2;
+
+    private static final int REPORTED_TO_POLICY_UNREPORTED = -1;
+    private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0;
+    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1;
+    private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
+    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
+
+    private static final int RINGBUFFER_MAX = 100;
+
+    private final String mTag;
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+
+    // Our handler.
+    private final DisplayControllerHandler mHandler;
+
+    // Battery stats.
+    @Nullable
+    private final IBatteryStats mBatteryStats;
+
+    // The sensor manager.
+    private final SensorManager mSensorManager;
+
+    // The window manager policy.
+    private final WindowManagerPolicy mWindowManagerPolicy;
+
+    // The display blanker.
+    private final DisplayBlanker mBlanker;
+
+    // The LogicalDisplay tied to this DisplayPowerController2.
+    private final LogicalDisplay mLogicalDisplay;
+
+    // The ID of the LogicalDisplay tied to this DisplayPowerController2.
+    private final int mDisplayId;
+
+    // The unique ID of the primary display device currently tied to this logical display
+    private String mUniqueDisplayId;
+
+    // Tracker for brightness changes.
+    @Nullable
+    private final BrightnessTracker mBrightnessTracker;
+
+    // Tracker for brightness settings changes.
+    private final SettingsObserver mSettingsObserver;
+
+    // The proximity sensor, or null if not available or needed.
+    private Sensor mProximitySensor;
+
+    // The doze screen brightness.
+    private final float mScreenBrightnessDozeConfig;
+
+    // The dim screen brightness.
+    private final float mScreenBrightnessDimConfig;
+
+    // The minimum dim amount to use if the screen brightness is already below
+    // mScreenBrightnessDimConfig.
+    private final float mScreenBrightnessMinimumDimAmount;
+
+    private final float mScreenBrightnessDefault;
+
+    // The minimum allowed brightness while in VR.
+    private final float mScreenBrightnessForVrRangeMinimum;
+
+    // The maximum allowed brightness while in VR.
+    private final float mScreenBrightnessForVrRangeMaximum;
+
+    // The default screen brightness for VR.
+    private final float mScreenBrightnessForVrDefault;
+
+    // True if auto-brightness should be used.
+    private boolean mUseSoftwareAutoBrightnessConfig;
+
+    // True if should use light sensor to automatically determine doze screen brightness.
+    private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+    // Whether or not the color fade on screen on / off is enabled.
+    private final boolean mColorFadeEnabled;
+
+    @GuardedBy("mCachedBrightnessInfo")
+    private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
+
+    private DisplayDevice mDisplayDevice;
+
+    // True if we should fade the screen while turning it off, false if we should play
+    // a stylish color fade animation instead.
+    private final boolean mColorFadeFadesConfig;
+
+    // True if we need to fake a transition to off when coming out of a doze state.
+    // Some display hardware will blank itself when coming out of doze in order to hide
+    // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
+    // blank itself and begin an appropriate power on animation.
+    private final boolean mDisplayBlanksAfterDozeConfig;
+
+    // True if there are only buckets of brightness values when the display is in the doze state,
+    // rather than a full range of values. If this is true, then we'll avoid animating the screen
+    // brightness since it'd likely be multiple jarring brightness transitions instead of just one
+    // to reach the final state.
+    private final boolean mBrightnessBucketsInDozeConfig;
+
+    private final Clock mClock;
+    private final Injector mInjector;
+
+    //  Maximum time a ramp animation can take.
+    private long mBrightnessRampIncreaseMaxTimeMillis;
+    private long mBrightnessRampDecreaseMaxTimeMillis;
+
+    // The pending power request.
+    // Initially null until the first call to requestPowerState.
+    @GuardedBy("mLock")
+    private DisplayPowerRequest mPendingRequestLocked;
+
+    // True if a request has been made to wait for the proximity sensor to go negative.
+    @GuardedBy("mLock")
+    private boolean mPendingWaitForNegativeProximityLocked;
+
+    // True if the pending power request or wait for negative proximity flag
+    // has been changed since the last update occurred.
+    @GuardedBy("mLock")
+    private boolean mPendingRequestChangedLocked;
+
+    // Set to true when the important parts of the pending power request have been applied.
+    // The important parts are mainly the screen state.  Brightness changes may occur
+    // concurrently.
+    @GuardedBy("mLock")
+    private boolean mDisplayReadyLocked;
+
+    // Set to true if a power state update is required.
+    @GuardedBy("mLock")
+    private boolean mPendingUpdatePowerStateLocked;
+
+    /* The following state must only be accessed by the handler thread. */
+
+    // The currently requested power state.
+    // The power controller will progressively update its internal state to match
+    // the requested power state.  Initially null until the first update.
+    private DisplayPowerRequest mPowerRequest;
+
+    // The current power state.
+    // Must only be accessed on the handler thread.
+    private DisplayPowerState mPowerState;
+
+    // True if the device should wait for negative proximity sensor before
+    // waking up the screen.  This is set to false as soon as a negative
+    // proximity sensor measurement is observed or when the device is forced to
+    // go to sleep by the user.  While true, the screen remains off.
+    private boolean mWaitingForNegativeProximity;
+
+    // True if the device should not take into account the proximity sensor
+    // until either the proximity sensor state changes, or there is no longer a
+    // request to listen to proximity sensor.
+    private boolean mIgnoreProximityUntilChanged;
+
+    // The actual proximity sensor threshold value.
+    private float mProximityThreshold;
+
+    // Set to true if the proximity sensor listener has been registered
+    // with the sensor manager.
+    private boolean mProximitySensorEnabled;
+
+    // The debounced proximity sensor state.
+    private int mProximity = PROXIMITY_UNKNOWN;
+
+    // The raw non-debounced proximity sensor state.
+    private int mPendingProximity = PROXIMITY_UNKNOWN;
+
+    // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
+    // be removed. Applies for both positive and negative proximity flips.
+    private long mPendingProximityDebounceTime = -1;
+
+    // True if the screen was turned off because of the proximity sensor.
+    // When the screen turns on again, we report user activity to the power manager.
+    private boolean mScreenOffBecauseOfProximity;
+
+    // The currently active screen on unblocker.  This field is non-null whenever
+    // we are waiting for a callback to release it and unblock the screen.
+    private ScreenOnUnblocker mPendingScreenOnUnblocker;
+    private ScreenOffUnblocker mPendingScreenOffUnblocker;
+
+    // True if we were in the process of turning off the screen.
+    // This allows us to recover more gracefully from situations where we abort
+    // turning off the screen.
+    private boolean mPendingScreenOff;
+
+    // The elapsed real time when the screen on was blocked.
+    private long mScreenOnBlockStartRealTime;
+    private long mScreenOffBlockStartRealTime;
+
+    // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
+    private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+
+    // If the last recorded screen state was dozing or not.
+    private boolean mDozing;
+
+    // Remembers whether certain kinds of brightness adjustments
+    // were recently applied so that we can decide how to transition.
+    private boolean mAppliedAutoBrightness;
+    private boolean mAppliedDimming;
+    private boolean mAppliedLowPower;
+    private boolean mAppliedScreenBrightnessOverride;
+    private boolean mAppliedTemporaryBrightness;
+    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+    private boolean mAppliedBrightnessBoost;
+    private boolean mAppliedThrottling;
+
+    // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
+    // information.
+    // At the time of this writing, this value is changed within updatePowerState() only, which is
+    // limited to the thread used by DisplayControllerHandler.
+    private final BrightnessReason mBrightnessReason = new BrightnessReason();
+    private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
+
+    // Brightness animation ramp rates in brightness units per second
+    private float mBrightnessRampRateFastDecrease;
+    private float mBrightnessRampRateFastIncrease;
+    private float mBrightnessRampRateSlowDecrease;
+    private float mBrightnessRampRateSlowIncrease;
+
+    // Report HBM brightness change to StatsD
+    private int mDisplayStatsId;
+    private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
+
+    // Whether or not to skip the initial brightness ramps into STATE_ON.
+    private final boolean mSkipScreenOnBrightnessRamp;
+
+    // Display white balance components.
+    @Nullable
+    private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
+    @Nullable
+    private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
+
+    @Nullable
+    private final ColorDisplayServiceInternal mCdsi;
+    private float[] mNitsRange;
+
+    private final HighBrightnessModeController mHbmController;
+
+    private final BrightnessThrottler mBrightnessThrottler;
+
+    private final BrightnessSetting mBrightnessSetting;
+
+    private final Runnable mOnBrightnessChangeRunnable;
+
+    private final BrightnessEvent mLastBrightnessEvent;
+    private final BrightnessEvent mTempBrightnessEvent;
+
+    // Keeps a record of brightness changes for dumpsys.
+    private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
+
+    // Controls and tracks all the wakelocks that are acquired/released by the system. Also acts as
+    // a medium of communication between this class and the PowerManagerService.
+    private final WakelockController mWakelockController;
+
+    // A record of state for skipping brightness ramps.
+    private int mSkipRampState = RAMP_STATE_SKIP_NONE;
+
+    // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
+    private float mInitialAutoBrightness;
+
+    // The controller for the automatic brightness level.
+    @Nullable
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private Sensor mLightSensor;
+
+    // The mappers between ambient lux, display backlight values, and display brightness.
+    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
+    // Mapper used for active (normal) screen brightness mode
+    @Nullable
+    private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+    // Mapper used for idle screen brightness mode
+    @Nullable
+    private BrightnessMappingStrategy mIdleModeBrightnessMapper;
+
+    // The current brightness configuration.
+    @Nullable
+    private BrightnessConfiguration mBrightnessConfiguration;
+
+    // The last brightness that was set by the user and not temporary. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+    private float mLastUserSetScreenBrightness = Float.NaN;
+
+    // The screen brightness setting has changed but not taken effect yet. If this is different
+    // from the current screen brightness setting then this is coming from something other than us
+    // and should be considered a user interaction.
+    private float mPendingScreenBrightnessSetting;
+
+    // The last observed screen brightness setting, either set by us or by the settings app on
+    // behalf of the user.
+    private float mCurrentScreenBrightnessSetting;
+
+    // The temporary screen brightness. Typically set when a user is interacting with the
+    // brightness slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
+    private float mTemporaryScreenBrightness;
+
+    // The current screen brightness while in VR mode.
+    private float mScreenBrightnessForVr;
+
+    // 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;
+
+    // The pending auto brightness adjustment that will take effect on the next power state update.
+    private float mPendingAutoBrightnessAdjustment;
+
+    // The temporary auto brightness adjustment. Typically set when a user is interacting with the
+    // adjustment slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+    private float mTemporaryAutoBrightnessAdjustment;
+
+    private boolean mIsRbcActive;
+
+    // Animators.
+    private ObjectAnimator mColorFadeOnAnimator;
+    private ObjectAnimator mColorFadeOffAnimator;
+    private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+    private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
+
+    // True if this DisplayPowerController2 has been stopped and should no longer be running.
+    private boolean mStopped;
+
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+
+    /**
+     * Creates the display power controller.
+     */
+    DisplayPowerController2(Context context, Injector injector,
+            DisplayPowerCallbacks callbacks, Handler handler,
+            SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
+            BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
+            Runnable onBrightnessChangeRunnable) {
+
+        mInjector = injector != null ? injector : new Injector();
+        mClock = mInjector.getClock();
+        mLogicalDisplay = logicalDisplay;
+        mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+        mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
+        mTag = "DisplayPowerController2[" + mDisplayId + "]";
+
+        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        mDisplayStatsId = mUniqueDisplayId.hashCode();
+        mHandler = new DisplayControllerHandler(handler.getLooper());
+        mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
+        mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mBatteryStats = BatteryStatsService.getService();
+        } else {
+            mBatteryStats = null;
+        }
+
+        mSettingsObserver = new SettingsObserver(mHandler);
+        mSensorManager = sensorManager;
+        mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
+        mBlanker = blanker;
+        mContext = context;
+        mBrightnessTracker = brightnessTracker;
+        // TODO: b/186428377 update brightness setting when display changes
+        mBrightnessSetting = brightnessSetting;
+        mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+
+        PowerManager pm = context.getSystemService(PowerManager.class);
+
+        final Resources resources = context.getResources();
+
+        // DOZE AND DIM SETTINGS
+        mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+        mScreenBrightnessDimConfig = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+        mScreenBrightnessMinimumDimAmount = resources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+
+
+        // NORMAL SCREEN SETTINGS
+        mScreenBrightnessDefault = clampAbsoluteBrightness(
+                mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
+
+        // VR SETTINGS
+        mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
+        mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
+        mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
+
+        // Check the setting, but also verify that it is the default display. Only the default
+        // display has an automatic brightness controller running.
+        // TODO: b/179021925 - Fix to work with multiple displays
+        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
+                R.bool.config_automatic_brightness_available)
+                && mDisplayId == Display.DEFAULT_DISPLAY;
+
+        mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
+                R.bool.config_allowAutoBrightnessWhileDozing);
+
+        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceConfig();
+
+        loadBrightnessRampRates();
+        mSkipScreenOnBrightnessRamp = resources.getBoolean(
+                R.bool.config_skipScreenOnBrightnessRamp);
+
+        mHbmController = createHbmControllerLocked();
+
+        mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+        // Seed the cached brightness
+        saveBrightnessInfo(getScreenBrightnessSetting());
+
+        DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
+        DisplayWhiteBalanceController displayWhiteBalanceController = null;
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            try {
+                displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
+                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
+                        mSensorManager, resources);
+                displayWhiteBalanceSettings.setCallbacks(this);
+                displayWhiteBalanceController.setCallbacks(this);
+            } catch (Exception e) {
+                Slog.e(mTag, "failed to set up display white-balance: " + e);
+            }
+        }
+        mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
+        mDisplayWhiteBalanceController = displayWhiteBalanceController;
+
+        loadNitsRange(resources);
+
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
+            boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
+                @Override
+                public void onReduceBrightColorsActivationChanged(boolean activated,
+                        boolean userInitiated) {
+                    applyReduceBrightColorsSplineAdjustment();
+
+                }
+
+                @Override
+                public void onReduceBrightColorsStrengthChanged(int strength) {
+                    applyReduceBrightColorsSplineAdjustment();
+                }
+            });
+            if (active) {
+                applyReduceBrightColorsSplineAdjustment();
+            }
+        } else {
+            mCdsi = null;
+        }
+
+        setUpAutoBrightness(resources, handler);
+
+        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeFadesConfig = resources.getBoolean(
+                R.bool.config_animateScreenLights);
+
+        mDisplayBlanksAfterDozeConfig = resources.getBoolean(
+                R.bool.config_displayBlanksAfterDoze);
+
+        mBrightnessBucketsInDozeConfig = resources.getBoolean(
+                R.bool.config_displayBrightnessBucketsInDoze);
+
+        loadProximitySensor();
+
+        mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+
+    }
+
+    private void applyReduceBrightColorsSplineAdjustment() {
+        mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget();
+        sendUpdatePowerState();
+    }
+
+    private void handleRbcChanged() {
+        if (mAutomaticBrightnessController == null) {
+            return;
+        }
+        if ((!mAutomaticBrightnessController.isInIdleMode()
+                && mInteractiveModeBrightnessMapper == null)
+                || (mAutomaticBrightnessController.isInIdleMode()
+                && mIdleModeBrightnessMapper == null)) {
+            Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
+            return;
+        }
+
+        float[] adjustedNits = new float[mNitsRange.length];
+        for (int i = 0; i < mNitsRange.length; i++) {
+            adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
+        }
+        mIsRbcActive = mCdsi.isReduceBrightColorsActivated();
+        mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits);
+    }
+
+    /**
+     * Returns true if the proximity sensor screen-off function is available.
+     */
+    @Override
+    public boolean isProximitySensorAvailable() {
+        return mProximitySensor != null;
+    }
+
+    /**
+     * Get the {@link BrightnessChangeEvent}s for the specified user.
+     *
+     * @param userId         userId to fetch data for
+     * @param includePackage if false will null out the package name in events
+     */
+    @Nullable
+    @Override
+    public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
+            @UserIdInt int userId, boolean includePackage) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
+        return mBrightnessTracker.getEvents(userId, includePackage);
+    }
+
+    @Override
+    public void onSwitchUser(@UserIdInt int newUserId) {
+        handleSettingsChange(true /* userSwitch */);
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.onSwitchUser(newUserId);
+        }
+    }
+
+    @Nullable
+    @Override
+    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
+            @UserIdInt int userId) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
+        return mBrightnessTracker.getAmbientBrightnessStats(userId);
+    }
+
+    /**
+     * Persist the brightness slider events and ambient brightness stats to disk.
+     */
+    @Override
+    public void persistBrightnessTrackerState() {
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.persistBrightnessTrackerState();
+        }
+    }
+
+    /**
+     * Requests a new power state.
+     * The controller makes a copy of the provided object and then
+     * begins adjusting the power state to match what was requested.
+     *
+     * @param request                  The requested power state.
+     * @param waitForNegativeProximity If true, issues a request to wait for
+     *                                 negative proximity before turning the screen back on,
+     *                                 assuming the screen
+     *                                 was turned off by the proximity sensor.
+     * @return True if display is ready, false if there are important changes that must
+     * be made asynchronously (such as turning the screen on), in which case the caller
+     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
+     * then try the request again later until the state converges.
+     */
+    public boolean requestPowerState(DisplayPowerRequest request,
+            boolean waitForNegativeProximity) {
+        if (DEBUG) {
+            Slog.d(mTag, "requestPowerState: "
+                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
+        }
+
+        synchronized (mLock) {
+            if (mStopped) {
+                return true;
+            }
+
+            boolean changed = false;
+
+            if (waitForNegativeProximity
+                    && !mPendingWaitForNegativeProximityLocked) {
+                mPendingWaitForNegativeProximityLocked = true;
+                changed = true;
+            }
+
+            if (mPendingRequestLocked == null) {
+                mPendingRequestLocked = new DisplayPowerRequest(request);
+                changed = true;
+            } else if (!mPendingRequestLocked.equals(request)) {
+                mPendingRequestLocked.copyFrom(request);
+                changed = true;
+            }
+
+            if (changed) {
+                mDisplayReadyLocked = false;
+                if (!mPendingRequestChangedLocked) {
+                    mPendingRequestChangedLocked = true;
+                    sendUpdatePowerStateLocked();
+                }
+            }
+
+            return mDisplayReadyLocked;
+        }
+    }
+
+    @Override
+    public BrightnessConfiguration getDefaultBrightnessConfiguration() {
+        if (mAutomaticBrightnessController == null) {
+            return null;
+        }
+        return mAutomaticBrightnessController.getDefaultConfig();
+    }
+
+    /**
+     * Notified when the display is changed. We use this to apply any changes that might be needed
+     * when displays get swapped on foldable devices.  For example, different brightness properties
+     * of each display need to be properly reflected in AutomaticBrightnessController.
+     *
+     * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
+     */
+    @Override
+    public void onDisplayChanged() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        if (device == null) {
+            Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
+                    + mLogicalDisplay.getDisplayIdLocked());
+            return;
+        }
+
+        final String uniqueId = device.getUniqueId();
+        final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
+        final IBinder token = device.getDisplayTokenLocked();
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        mHandler.post(() -> {
+            if (mDisplayDevice != device) {
+                mDisplayDevice = device;
+                mUniqueDisplayId = uniqueId;
+                mDisplayStatsId = mUniqueDisplayId.hashCode();
+                mDisplayDeviceConfig = config;
+                loadFromDisplayDeviceConfig(token, info);
+                updatePowerState();
+            }
+        });
+    }
+
+    /**
+     * Called when the displays are preparing to transition from one device state to another.
+     * This process involves turning off some displays so we need updatePowerState() to run and
+     * calculate the new state.
+     */
+    @Override
+    public void onDeviceStateTransition() {
+        sendUpdatePowerState();
+    }
+
+    /**
+     * Unregisters all listeners and interrupts all running threads; halting future work.
+     *
+     * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
+     * the {@link #mDisplayId display} has been removed.
+     */
+    @Override
+    public void stop() {
+        synchronized (mLock) {
+            mStopped = true;
+            Message msg = mHandler.obtainMessage(MSG_STOP);
+            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+
+            if (mDisplayWhiteBalanceController != null) {
+                mDisplayWhiteBalanceController.setEnabled(false);
+            }
+
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.stop();
+            }
+
+            if (mBrightnessSetting != null) {
+                mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+            }
+
+            mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+        }
+    }
+
+    private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+        // All properties that depend on the associated DisplayDevice and the DDC must be
+        // updated here.
+        loadBrightnessRampRates();
+        loadProximitySensor();
+        loadNitsRange(mContext.getResources());
+        setUpAutoBrightness(mContext.getResources(), mHandler);
+        reloadReduceBrightColours();
+        if (mScreenBrightnessRampAnimator != null) {
+            mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+                    mBrightnessRampIncreaseMaxTimeMillis,
+                    mBrightnessRampDecreaseMaxTimeMillis);
+        }
+        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+                mDisplayDeviceConfig.getHighBrightnessModeData(),
+                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+                    @Override
+                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    }
+                });
+        mBrightnessThrottler.resetThrottlingData(
+                mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+    }
+
+    private void sendUpdatePowerState() {
+        synchronized (mLock) {
+            sendUpdatePowerStateLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void sendUpdatePowerStateLocked() {
+        if (!mStopped && !mPendingUpdatePowerStateLocked) {
+            mPendingUpdatePowerStateLocked = true;
+            Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
+            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+        }
+    }
+
+    private void initialize(int displayState) {
+        mPowerState = mInjector.getDisplayPowerState(mBlanker,
+                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
+
+        if (mColorFadeEnabled) {
+            mColorFadeOnAnimator = ObjectAnimator.ofFloat(
+                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
+            mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
+            mColorFadeOnAnimator.addListener(mAnimatorListener);
+
+            mColorFadeOffAnimator = ObjectAnimator.ofFloat(
+                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
+            mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
+            mColorFadeOffAnimator.addListener(mAnimatorListener);
+        }
+
+        mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
+                DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
+                DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
+        mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+                mBrightnessRampIncreaseMaxTimeMillis,
+                mBrightnessRampDecreaseMaxTimeMillis);
+        mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
+
+        noteScreenState(mPowerState.getScreenState());
+        noteScreenBrightness(mPowerState.getScreenBrightness());
+
+        // Initialize all of the brightness tracking state
+        final float brightness = convertToNits(mPowerState.getScreenBrightness());
+        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+            mBrightnessTracker.start(brightness);
+        }
+        mBrightnessSettingListener = brightnessValue -> {
+            Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
+            mHandler.sendMessage(msg);
+        };
+
+        mBrightnessSetting.registerListener(mBrightnessSettingListener);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+    }
+
+    private void setUpAutoBrightness(Resources resources, Handler handler) {
+        if (!mUseSoftwareAutoBrightnessConfig) {
+            return;
+        }
+
+        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+                R.bool.config_enableIdleScreenBrightnessMode);
+        mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+                mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+        if (isIdleScreenBrightnessEnabled) {
+            mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
+                    mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+        }
+
+        if (mInteractiveModeBrightnessMapper != null) {
+            final float dozeScaleFactor = resources.getFraction(
+                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                    1, 1);
+
+            // Ambient Lux - Active Mode Brightness Thresholds
+            float[] ambientBrighteningThresholds =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+            float[] ambientDarkeningThresholds =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+            float[] ambientBrighteningLevels =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevels();
+            float[] ambientDarkeningLevels =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevels();
+            float ambientDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+            float ambientBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
+            HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+                    ambientBrighteningThresholds, ambientDarkeningThresholds,
+                    ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningMinThreshold);
+
+            // Display - Active Mode Brightness Thresholds
+            float[] screenBrighteningThresholds =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentages();
+            float[] screenDarkeningThresholds =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentages();
+            float[] screenBrighteningLevels =
+                    mDisplayDeviceConfig.getScreenBrighteningLevels();
+            float[] screenDarkeningLevels =
+                    mDisplayDeviceConfig.getScreenDarkeningLevels();
+            float screenDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+            float screenBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
+            HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+                    screenBrighteningThresholds, screenDarkeningThresholds,
+                    screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+                    screenBrighteningMinThreshold, true);
+
+            // Ambient Lux - Idle Screen Brightness Thresholds
+            float ambientDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+            float ambientBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+            float[] ambientBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+            float[] ambientDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+            float[] ambientBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+            float[] ambientDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
+            HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+                    ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+                    ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+                    ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+            // Display - Idle Screen Brightness Thresholds
+            float screenDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+            float screenBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+            float[] screenBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+            float[] screenDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+            float[] screenBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+            float[] screenDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+                    screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+                    screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+            long brighteningLightDebounce = mDisplayDeviceConfig
+                    .getAutoBrightnessBrighteningLightDebounce();
+            long darkeningLightDebounce = mDisplayDeviceConfig
+                    .getAutoBrightnessDarkeningLightDebounce();
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+                    R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
+            int lightSensorWarmUpTimeConfig = resources.getInteger(
+                    R.integer.config_lightSensorWarmupTime);
+            int lightSensorRate = resources.getInteger(
+                    R.integer.config_autoBrightnessLightSensorRate);
+            int initialLightSensorRate = resources.getInteger(
+                    R.integer.config_autoBrightnessInitialLightSensorRate);
+            if (initialLightSensorRate == -1) {
+                initialLightSensorRate = lightSensorRate;
+            } else if (initialLightSensorRate > lightSensorRate) {
+                Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+                        + initialLightSensorRate + ") to be less than or equal to "
+                        + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
+            }
+
+            loadAmbientLightSensor();
+            if (mBrightnessTracker != null) {
+                mBrightnessTracker.setLightSensor(mLightSensor);
+            }
+
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.stop();
+            }
+            mAutomaticBrightnessController = new AutomaticBrightnessController(this,
+                    handler.getLooper(), mSensorManager, mLightSensor,
+                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
+                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
+                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
+                    darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
+                    ambientBrightnessThresholds, screenBrightnessThresholds,
+                    ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
+                    mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+                    mDisplayDeviceConfig.getAmbientHorizonShort(),
+                    mDisplayDeviceConfig.getAmbientHorizonLong());
+
+            mBrightnessEventRingBuffer =
+                    new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+        } else {
+            mUseSoftwareAutoBrightnessConfig = false;
+        }
+    }
+
+    private void loadBrightnessRampRates() {
+        mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease();
+        mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease();
+        mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease();
+        mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease();
+        mBrightnessRampDecreaseMaxTimeMillis =
+                mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis();
+        mBrightnessRampIncreaseMaxTimeMillis =
+                mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis();
+    }
+
+    private void loadNitsRange(Resources resources) {
+        if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
+            mNitsRange = mDisplayDeviceConfig.getNits();
+        } else {
+            Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
+            mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
+                    .obtainTypedArray(R.array.config_screenBrightnessNits));
+        }
+    }
+
+    private void reloadReduceBrightColours() {
+        if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
+            applyReduceBrightColorsSplineAdjustment();
+        }
+    }
+
+    @Override
+    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+        if (mAutomaticBrightnessController != null) {
+            if (isIdle) {
+                mAutomaticBrightnessController.switchToIdleMode();
+            } else {
+                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
+            }
+        }
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            sendUpdatePowerState();
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+    };
+
+    private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
+        @Override
+        public void onAnimationEnd() {
+            sendUpdatePowerState();
+            Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
+            mHandler.sendMessage(msg);
+        }
+    };
+
+    /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
+    private void cleanupHandlerThreadAfterStop() {
+        setProximitySensorEnabled(false);
+        mHbmController.stop();
+        mBrightnessThrottler.stop();
+        mHandler.removeCallbacksAndMessages(null);
+
+        // Release any outstanding wakelocks we're still holding because of pending messages.
+        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+        mWakelockController.releaseStateChangedSuspendBlocker();
+        mWakelockController.releaseProxPositiveSuspendBlocker();
+        mWakelockController.releaseProxNegativeSuspendBlocker();
+
+        final float brightness = mPowerState != null
+                ? mPowerState.getScreenBrightness()
+                : PowerManager.BRIGHTNESS_MIN;
+        reportStats(brightness);
+
+        if (mPowerState != null) {
+            mPowerState.stop();
+            mPowerState = null;
+        }
+    }
+
+    private void updatePowerState() {
+        Trace.traceBegin(Trace.TRACE_TAG_POWER,
+                "DisplayPowerController#updatePowerState");
+        updatePowerStateInternal();
+        Trace.traceEnd(Trace.TRACE_TAG_POWER);
+    }
+
+    private void updatePowerStateInternal() {
+        // Update the power state request.
+        final boolean mustNotify;
+        final int previousPolicy;
+        boolean mustInitialize = false;
+        int brightnessAdjustmentFlags = 0;
+        mBrightnessReasonTemp.set(null);
+        mTempBrightnessEvent.reset();
+        synchronized (mLock) {
+            if (mStopped) {
+                return;
+            }
+            mPendingUpdatePowerStateLocked = false;
+            if (mPendingRequestLocked == null) {
+                return; // wait until first actual power request
+            }
+
+            if (mPowerRequest == null) {
+                mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
+                updatePendingProximityRequestsLocked();
+                mPendingRequestChangedLocked = false;
+                mustInitialize = true;
+                // Assume we're on and bright until told otherwise, since that's the state we turn
+                // on in.
+                previousPolicy = DisplayPowerRequest.POLICY_BRIGHT;
+            } else if (mPendingRequestChangedLocked) {
+                previousPolicy = mPowerRequest.policy;
+                mPowerRequest.copyFrom(mPendingRequestLocked);
+                updatePendingProximityRequestsLocked();
+                mPendingRequestChangedLocked = false;
+                mDisplayReadyLocked = false;
+            } else {
+                previousPolicy = mPowerRequest.policy;
+            }
+
+            mustNotify = !mDisplayReadyLocked;
+        }
+
+        // Compute the basic display state using the policy.
+        // We might override this below based on other factors.
+        // Initialise brightness as invalid.
+        int state;
+        float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        boolean performScreenOffTransition = false;
+        switch (mPowerRequest.policy) {
+            case DisplayPowerRequest.POLICY_OFF:
+                state = Display.STATE_OFF;
+                performScreenOffTransition = true;
+                break;
+            case DisplayPowerRequest.POLICY_DOZE:
+                if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+                    state = mPowerRequest.dozeScreenState;
+                } else {
+                    state = Display.STATE_DOZE;
+                }
+                if (!mAllowAutoBrightnessWhileDozingConfig) {
+                    brightnessState = mPowerRequest.dozeScreenBrightness;
+                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
+                }
+                break;
+            case DisplayPowerRequest.POLICY_VR:
+                state = Display.STATE_VR;
+                break;
+            case DisplayPowerRequest.POLICY_DIM:
+            case DisplayPowerRequest.POLICY_BRIGHT:
+            default:
+                state = Display.STATE_ON;
+                break;
+        }
+        assert (state != Display.STATE_UNKNOWN);
+
+        boolean skipRampBecauseOfProximityChangeToNegative = false;
+        // Apply the proximity sensor.
+        if (mProximitySensor != null) {
+            if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
+                // At this point the policy says that the screen should be on, but we've been
+                // asked to listen to the prox sensor to adjust the display state, so lets make
+                // sure the sensor is on.
+                setProximitySensorEnabled(true);
+                if (!mScreenOffBecauseOfProximity
+                        && mProximity == PROXIMITY_POSITIVE
+                        && !mIgnoreProximityUntilChanged) {
+                    // Prox sensor already reporting "near" so we should turn off the screen.
+                    // Also checked that we aren't currently set to ignore the proximity sensor
+                    // temporarily.
+                    mScreenOffBecauseOfProximity = true;
+                    sendOnProximityPositiveWithWakelock();
+                }
+            } else if (mWaitingForNegativeProximity
+                    && mScreenOffBecauseOfProximity
+                    && mProximity == PROXIMITY_POSITIVE
+                    && state != Display.STATE_OFF) {
+                // The policy says that we should have the screen on, but it's off due to the prox
+                // and we've been asked to wait until the screen is far from the user to turn it
+                // back on. Let keep the prox sensor on so we can tell when it's far again.
+                setProximitySensorEnabled(true);
+            } else {
+                // We haven't been asked to use the prox sensor and we're not waiting on the screen
+                // to turn back on...so lets shut down the prox sensor.
+                setProximitySensorEnabled(false);
+                mWaitingForNegativeProximity = false;
+            }
+
+            if (mScreenOffBecauseOfProximity
+                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+                // The screen *was* off due to prox being near, but now it's "far" so lets turn
+                // the screen back on.  Also turn it back on if we've been asked to ignore the
+                // prox sensor temporarily.
+                mScreenOffBecauseOfProximity = false;
+                skipRampBecauseOfProximityChangeToNegative = true;
+                sendOnProximityNegativeWithWakelock();
+            }
+        } else {
+            mWaitingForNegativeProximity = false;
+            mIgnoreProximityUntilChanged = false;
+        }
+
+        if (!mLogicalDisplay.isEnabled()
+                || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+                || mScreenOffBecauseOfProximity) {
+            state = Display.STATE_OFF;
+        }
+
+        // Initialize things the first time the power state is changed.
+        if (mustInitialize) {
+            initialize(state);
+        }
+
+        // Animate the screen state change unless already animating.
+        // The transition may be deferred, so after this point we will use the
+        // actual state instead of the desired one.
+        final int oldState = mPowerState.getScreenState();
+        animateScreenStateChange(state, performScreenOffTransition);
+        state = mPowerState.getScreenState();
+
+        if (state == Display.STATE_OFF) {
+            brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
+        }
+
+        // Always use the VR brightness when in the VR state.
+        if (state == Display.STATE_VR) {
+            brightnessState = mScreenBrightnessForVr;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
+        }
+
+        if ((Float.isNaN(brightnessState))
+                && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
+            brightnessState = mPowerRequest.screenBrightnessOverride;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
+            mAppliedScreenBrightnessOverride = true;
+        } else {
+            mAppliedScreenBrightnessOverride = false;
+        }
+
+        final boolean autoBrightnessEnabledInDoze =
+                mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+                && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
+                && Float.isNaN(brightnessState)
+                && mAutomaticBrightnessController != null;
+        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+                && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+        final int autoBrightnessState = autoBrightnessEnabled
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : autoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+        final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+
+        // Use the temporary screen brightness if there isn't an override, either from
+        // WindowManager or based on the display state.
+        if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
+            brightnessState = mTemporaryScreenBrightness;
+            mAppliedTemporaryBrightness = true;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
+        } else {
+            mAppliedTemporaryBrightness = false;
+        }
+
+        final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
+
+        // Use the autobrightness adjustment override if set.
+        final float autoBrightnessAdjustment;
+        if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
+            autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
+            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
+            mAppliedTemporaryAutoBrightnessAdjustment = true;
+        } else {
+            autoBrightnessAdjustment = mAutoBrightnessAdjustment;
+            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
+            mAppliedTemporaryAutoBrightnessAdjustment = false;
+        }
+        // Apply brightness boost.
+        // We do this here after deciding whether auto-brightness is enabled so that we don't
+        // disable the light sensor during this temporary state.  That way when boost ends we will
+        // be able to resume normal auto-brightness behavior without any delay.
+        if (mPowerRequest.boostScreenBrightness
+                && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
+            brightnessState = PowerManager.BRIGHTNESS_MAX;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
+            mAppliedBrightnessBoost = true;
+        } else {
+            mAppliedBrightnessBoost = false;
+        }
+
+        // If the brightness is already set then it's been overridden by something other than the
+        // user, or is a temporary adjustment.
+        boolean userInitiatedChange = (Float.isNaN(brightnessState))
+                && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
+        boolean hadUserBrightnessPoint = false;
+        // Configure auto-brightness.
+        if (mAutomaticBrightnessController != null) {
+            hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+            mAutomaticBrightnessController.configure(autoBrightnessState,
+                    mBrightnessConfiguration,
+                    mLastUserSetScreenBrightness,
+                    userSetBrightnessChanged, autoBrightnessAdjustment,
+                    autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+        }
+
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+        }
+
+        boolean updateScreenBrightnessSetting = false;
+
+        // Apply auto-brightness.
+        boolean slowChange = false;
+        if (Float.isNaN(brightnessState)) {
+            float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
+            if (autoBrightnessEnabled) {
+                brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
+                        mTempBrightnessEvent);
+                newAutoBrightnessAdjustment =
+                        mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
+            }
+            if (isValidBrightnessValue(brightnessState)
+                    || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                // Use current auto-brightness value and slowly adjust to changes.
+                brightnessState = clampScreenBrightness(brightnessState);
+                if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
+                    slowChange = true; // slowly adapt to auto-brightness
+                }
+                updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+                mAppliedAutoBrightness = true;
+                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+            } else {
+                mAppliedAutoBrightness = false;
+            }
+            if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+                // If the autobrightness controller has decided to change the adjustment value
+                // used, make sure that's reflected in settings.
+                putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+            } else {
+                // Adjustment values resulted in no change
+                brightnessAdjustmentFlags = 0;
+            }
+        } else {
+            // Any non-auto-brightness values such as override or temporary should still be subject
+            // to clamping so that they don't go beyond the current max as specified by HBM
+            // Controller.
+            brightnessState = clampScreenBrightness(brightnessState);
+            mAppliedAutoBrightness = false;
+            brightnessAdjustmentFlags = 0;
+        }
+
+        // Use default brightness when dozing unless overridden.
+        if ((Float.isNaN(brightnessState))
+                && Display.isDozeState(state)) {
+            brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig);
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
+        }
+
+        // Apply manual brightness.
+        if (Float.isNaN(brightnessState)) {
+            brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+            if (brightnessState != mCurrentScreenBrightnessSetting) {
+                // The manually chosen screen brightness is outside of the currently allowed
+                // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+                // by updating the setting.
+                updateScreenBrightnessSetting = true;
+            }
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
+        }
+
+        // Now that a desired brightness has been calculated, apply brightness throttling. The
+        // dimming and low power transformations that follow can only dim brightness further.
+        //
+        // We didn't do this earlier through brightness clamping because we need to know both
+        // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+        // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+        // we broadcast this change through setting.
+        final float unthrottledBrightnessState = brightnessState;
+        if (mBrightnessThrottler.isThrottled()) {
+            mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
+            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            if (!mAppliedThrottling) {
+                // Brightness throttling is needed, so do so quickly.
+                // Later, when throttling is removed, we let other mechanisms decide on speed.
+                slowChange = false;
+            }
+            mAppliedThrottling = true;
+        } else if (mAppliedThrottling) {
+            mAppliedThrottling = false;
+        }
+
+        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
+            // before applying the low power or dim transformations so that the slider
+            // accurately represents the full possible range, even if they range changes what
+            // it means in absolute terms.
+            updateScreenBrightnessSetting(brightnessState);
+        }
+
+        // Apply dimming by at least some minimum amount when user activity
+        // timeout is about to expire.
+        if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
+            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+                brightnessState = Math.max(
+                        Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
+                                mScreenBrightnessDimConfig),
+                        PowerManager.BRIGHTNESS_MIN);
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
+            }
+            if (!mAppliedDimming) {
+                slowChange = false;
+            }
+            mAppliedDimming = true;
+        } else if (mAppliedDimming) {
+            slowChange = false;
+            mAppliedDimming = false;
+        }
+        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+        // as long as it is above the minimum threshold.
+        if (mPowerRequest.lowPowerMode) {
+            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+                final float brightnessFactor =
+                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
+                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
+                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+            }
+            if (!mAppliedLowPower) {
+                slowChange = false;
+            }
+            mAppliedLowPower = true;
+        } else if (mAppliedLowPower) {
+            slowChange = false;
+            mAppliedLowPower = false;
+        }
+
+        // The current brightness to use has been calculated at this point, and HbmController should
+        // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
+        // 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,
+                mBrightnessThrottler.getBrightnessMaxReason());
+
+        // Animate the screen brightness when the screen is on or dozing.
+        // Skip the animation when the screen is off or suspended or transition to/from VR.
+        boolean brightnessAdjusted = false;
+        final boolean brightnessIsTemporary =
+                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+        if (!mPendingScreenOff) {
+            if (mSkipScreenOnBrightnessRamp) {
+                if (state == Display.STATE_ON) {
+                    if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
+                        mInitialAutoBrightness = brightnessState;
+                        mSkipRampState = RAMP_STATE_SKIP_INITIAL;
+                    } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL
+                            && mUseSoftwareAutoBrightnessConfig
+                            && !BrightnessSynchronizer.floatEquals(brightnessState,
+                            mInitialAutoBrightness)) {
+                        mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT;
+                    } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) {
+                        mSkipRampState = RAMP_STATE_SKIP_NONE;
+                    }
+                } else {
+                    mSkipRampState = RAMP_STATE_SKIP_NONE;
+                }
+            }
+
+            final boolean wasOrWillBeInVr =
+                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
+            final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
+                    != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+            // While dozing, sometimes the brightness is split into buckets. Rather than animating
+            // through the buckets, which is unlikely to be smooth in the first place, just jump
+            // right to the suggested brightness.
+            final boolean hasBrightnessBuckets =
+                    Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+            // If the color fade is totally covering the screen then we can change the backlight
+            // level without it being a noticeable jump since any actual content isn't yet visible.
+            final boolean isDisplayContentVisible =
+                    mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+            // We only want to animate the brightness if it is between 0.0f and 1.0f.
+            // brightnessState can contain the values -1.0f and NaN, which we do not want to
+            // animate to. To avoid this, we check the value first.
+            // If the brightnessState is off (-1.0f) we still want to animate to the minimum
+            // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
+            // user even when the display is all black. We also clamp here in case some
+            // transformations to the brightness have pushed it outside of the currently
+            // allowed range.
+            float animateValue = clampScreenBrightness(brightnessState);
+
+            // 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
+                    && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
+                    && (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
+                    == 0) {
+                // We want to scale HDR brightness level with the SDR level
+                animateValue = mHbmController.getHdrBrightnessValue();
+            }
+
+            final float currentBrightness = mPowerState.getScreenBrightness();
+            final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+            if (isValidBrightnessValue(animateValue)
+                    && (animateValue != currentBrightness
+                    || sdrAnimateValue != currentSdrBrightness)) {
+                if (initialRampSkip || hasBrightnessBuckets
+                        || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+                    animateScreenBrightness(animateValue, sdrAnimateValue,
+                            SCREEN_ANIMATION_RATE_MINIMUM);
+                } else {
+                    boolean isIncreasing = animateValue > currentBrightness;
+                    final float rampSpeed;
+                    if (isIncreasing && slowChange) {
+                        rampSpeed = mBrightnessRampRateSlowIncrease;
+                    } else if (isIncreasing && !slowChange) {
+                        rampSpeed = mBrightnessRampRateFastIncrease;
+                    } else if (!isIncreasing && slowChange) {
+                        rampSpeed = mBrightnessRampRateSlowDecrease;
+                    } else {
+                        rampSpeed = mBrightnessRampRateFastDecrease;
+                    }
+                    animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
+                }
+            }
+
+            // Report brightness to brightnesstracker:
+            // If brightness is not temporary (ie the slider has been released)
+            // AND if we are not in idle screen brightness mode.
+            if (!brightnessIsTemporary
+                    && (mAutomaticBrightnessController != null
+                    && !mAutomaticBrightnessController.isInIdleMode())) {
+                if (userInitiatedChange && (mAutomaticBrightnessController == null
+                        || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+                    // If we don't have a valid lux reading we can't report a valid
+                    // slider event so notify as if the system changed the brightness.
+                    userInitiatedChange = false;
+                }
+                notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+                        hadUserBrightnessPoint);
+            }
+
+            // We save the brightness info *after* the brightness setting has been changed and
+            // adjustments made so that the brightness info reflects the latest value.
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+        } else {
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+        }
+
+        // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+        if (brightnessAdjusted && !brightnessIsTemporary) {
+            postBrightnessChangeRunnable();
+        }
+
+        // Log any changes to what is currently driving the brightness setting.
+        if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
+            Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
+                    + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
+                    + "', previous reason: '" + mBrightnessReason + "'.");
+            mBrightnessReason.set(mBrightnessReasonTemp);
+        } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
+                && userSetBrightnessChanged) {
+            Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
+        }
+
+
+        // Log brightness events when a detail of significance has changed. Generally this is the
+        // brightness itself changing, but also includes data like HBM cap, thermal throttling
+        // brightness cap, RBC state, etc.
+        mTempBrightnessEvent.setTime(System.currentTimeMillis());
+        mTempBrightnessEvent.setBrightness(brightnessState);
+        mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
+        mTempBrightnessEvent.setReason(mBrightnessReason);
+        mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
+        mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+        mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+                | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+        mTempBrightnessEvent.setRbcStrength(mCdsi != null
+                ? mCdsi.getReduceBrightColorsStrength() : -1);
+        mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        // 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 =
+                mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
+                        && mLastBrightnessEvent.getReason().getReason()
+                        == BrightnessReason.REASON_TEMPORARY;
+        if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
+                || brightnessAdjustmentFlags != 0) {
+            float lastBrightness = mLastBrightnessEvent.getBrightness();
+            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+            mTempBrightnessEvent.setFastAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
+            mTempBrightnessEvent.setSlowAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
+            BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
+            // Adjustment flags (and user-set flag) only get added after the equality checks since
+            // they are transient.
+            newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
+            newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
+                    ? BrightnessEvent.FLAG_USER_SET : 0));
+            Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
+
+            if (userSetBrightnessChanged) {
+                logManualBrightnessEvent(newEvent);
+            }
+            if (mBrightnessEventRingBuffer != null) {
+                mBrightnessEventRingBuffer.append(newEvent);
+            }
+        }
+
+        // Update display white-balance.
+        if (mDisplayWhiteBalanceController != null) {
+            if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
+                mDisplayWhiteBalanceController.setEnabled(true);
+                mDisplayWhiteBalanceController.updateDisplayColorTemperature();
+            } else {
+                mDisplayWhiteBalanceController.setEnabled(false);
+            }
+        }
+
+        // Determine whether the display is ready for use in the newly requested state.
+        // Note that we do not wait for the brightness ramp animation to complete before
+        // reporting the display is ready because we only need to ensure the screen is in the
+        // right power state even as it continues to converge on the desired brightness.
+        final boolean ready = mPendingScreenOnUnblocker == null
+                && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
+                        && !mColorFadeOffAnimator.isStarted()))
+                && mPowerState.waitUntilClean(mCleanListener);
+        final boolean finished = ready
+                && !mScreenBrightnessRampAnimator.isAnimating();
+
+        // Notify policy about screen turned on.
+        if (ready && state != Display.STATE_OFF
+                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
+            mWindowManagerPolicy.screenTurnedOn(mDisplayId);
+        }
+
+        // Grab a wake lock if we have unfinished business.
+        if (!finished) {
+            mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
+        }
+
+        // Notify the power manager when ready.
+        if (ready && mustNotify) {
+            // Send state change.
+            synchronized (mLock) {
+                if (!mPendingRequestChangedLocked) {
+                    mDisplayReadyLocked = true;
+
+                    if (DEBUG) {
+                        Slog.d(mTag, "Display ready!");
+                    }
+                }
+            }
+            sendOnStateChangedWithWakelock();
+        }
+
+        // Release the wake lock when we have no unfinished business.
+        if (finished) {
+            mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+        }
+
+        // Record if dozing for future comparison.
+        mDozing = state != Display.STATE_ON;
+
+        if (previousPolicy != mPowerRequest.policy) {
+            logDisplayPolicyChanged(mPowerRequest.policy);
+        }
+    }
+
+    @Override
+    public void updateBrightness() {
+        sendUpdatePowerState();
+    }
+
+    /**
+     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+     * currently enabled and forcing the screen to be dark.
+     */
+    @Override
+    public void ignoreProximitySensorUntilChanged() {
+        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+    }
+
+    @Override
+    public void setBrightnessConfiguration(BrightnessConfiguration c) {
+        Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public void setTemporaryBrightness(float brightness) {
+        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
+                Float.floatToIntBits(brightness), 0 /*unused*/);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
+                Float.floatToIntBits(adjustment), 0 /*unused*/);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public BrightnessInfo getBrightnessInfo() {
+        synchronized (mCachedBrightnessInfo) {
+            return new BrightnessInfo(
+                    mCachedBrightnessInfo.brightness.value,
+                    mCachedBrightnessInfo.adjustedBrightness.value,
+                    mCachedBrightnessInfo.brightnessMin.value,
+                    mCachedBrightnessInfo.brightnessMax.value,
+                    mCachedBrightnessInfo.hbmMode.value,
+                    mCachedBrightnessInfo.hbmTransitionPoint.value,
+                    mCachedBrightnessInfo.brightnessMaxReason.value);
+        }
+    }
+
+    private boolean saveBrightnessInfo(float brightness) {
+        return saveBrightnessInfo(brightness, brightness);
+    }
+
+    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+        synchronized (mCachedBrightnessInfo) {
+            final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+                    mBrightnessThrottler.getBrightnessCap());
+            final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+                    mBrightnessThrottler.getBrightnessCap());
+            boolean changed = false;
+
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
+                            brightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
+                            adjustedBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
+                            minBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
+                            maxBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
+                            mHbmController.getHighBrightnessMode());
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
+                            mHbmController.getTransitionPoint());
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+                            mBrightnessThrottler.getBrightnessMaxReason());
+
+            return changed;
+        }
+    }
+
+    void postBrightnessChangeRunnable() {
+        mHandler.post(mOnBrightnessChangeRunnable);
+    }
+
+    private HighBrightnessModeController createHbmControllerLocked() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+        final IBinder displayToken =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
+        final String displayUniqueId =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        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) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    }
+                },
+                () -> {
+                    sendUpdatePowerState();
+                    postBrightnessChangeRunnable();
+                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+                    if (mAutomaticBrightnessController != null) {
+                        mAutomaticBrightnessController.update();
+                    }
+                }, mContext);
+    }
+
+    private BrightnessThrottler createBrightnessThrottlerLocked() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+        final DisplayDeviceConfig.BrightnessThrottlingData data =
+                ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
+        return new BrightnessThrottler(mHandler, data,
+                () -> {
+                    sendUpdatePowerState();
+                    postBrightnessChangeRunnable();
+                }, mUniqueDisplayId);
+    }
+
+    private void blockScreenOn() {
+        if (mPendingScreenOnUnblocker == null) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+            mPendingScreenOnUnblocker = new ScreenOnUnblocker();
+            mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
+            Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
+        }
+    }
+
+    private void unblockScreenOn() {
+        if (mPendingScreenOnUnblocker != null) {
+            mPendingScreenOnUnblocker = null;
+            long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
+            Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+        }
+    }
+
+    private void blockScreenOff() {
+        if (mPendingScreenOffUnblocker == null) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+            mPendingScreenOffUnblocker = new ScreenOffUnblocker();
+            mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
+            Slog.i(mTag, "Blocking screen off");
+        }
+    }
+
+    private void unblockScreenOff() {
+        if (mPendingScreenOffUnblocker != null) {
+            mPendingScreenOffUnblocker = null;
+            long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
+            Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+        }
+    }
+
+    private boolean setScreenState(int state) {
+        return setScreenState(state, false /*reportOnly*/);
+    }
+
+    private boolean setScreenState(int state, boolean reportOnly) {
+        final boolean isOff = (state == Display.STATE_OFF);
+
+        if (mPowerState.getScreenState() != state
+                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+            // If we are trying to turn screen off, give policy a chance to do something before we
+            // actually turn the screen off.
+            if (isOff && !mScreenOffBecauseOfProximity) {
+                if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
+                        || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+                    setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
+                    blockScreenOff();
+                    mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
+                    unblockScreenOff();
+                } else if (mPendingScreenOffUnblocker != null) {
+                    // Abort doing the state change until screen off is unblocked.
+                    return false;
+                }
+            }
+
+            if (!reportOnly && mPowerState.getScreenState() != state) {
+                Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+                mPowerState.setScreenState(state);
+                // Tell battery stats about the transition.
+                noteScreenState(state);
+            }
+        }
+
+        // Tell the window manager policy when the screen is turned off or on unless it's due
+        // to the proximity sensor.  We temporarily block turning the screen on until the
+        // window manager is ready by leaving a black surface covering the screen.
+        // This surface is essentially the final state of the color fade animation and
+        // it is only removed once the window manager tells us that the activity has
+        // finished drawing underneath.
+        if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
+                && !mScreenOffBecauseOfProximity) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+            unblockScreenOn();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+        } else if (!isOff
+                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
+
+            // We told policy already that screen was turning off, but now we changed our minds.
+            // Complete the full state transition on -> turningOff -> off.
+            unblockScreenOff();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+        }
+        if (!isOff
+                && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
+                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
+            if (mPowerState.getColorFadeLevel() == 0.0f) {
+                blockScreenOn();
+            } else {
+                unblockScreenOn();
+            }
+            mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
+        }
+
+        // Return true if the screen isn't blocked.
+        return mPendingScreenOnUnblocker == null;
+    }
+
+    private void setReportedScreenState(int state) {
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
+        mReportedScreenStateToPolicy = state;
+    }
+
+    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);
+    }
+
+    private void loadProximitySensor() {
+        if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+            return;
+        }
+        final DisplayDeviceConfig.SensorData proxSensor =
+                mDisplayDeviceConfig.getProximitySensor();
+        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+                ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
+        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
+                fallbackType);
+        if (mProximitySensor != null) {
+            mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+                    TYPICAL_PROXIMITY_THRESHOLD);
+        }
+    }
+
+    private float clampScreenBrightnessForVr(float value) {
+        return MathUtils.constrain(
+                value, mScreenBrightnessForVrRangeMinimum,
+                mScreenBrightnessForVrRangeMaximum);
+    }
+
+    private float clampScreenBrightness(float value) {
+        if (Float.isNaN(value)) {
+            value = PowerManager.BRIGHTNESS_MIN;
+        }
+        return MathUtils.constrain(value,
+                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+    }
+
+    // Checks whether the brightness is within the valid brightness range, not including off.
+    private boolean isValidBrightnessValue(float brightness) {
+        return brightness >= PowerManager.BRIGHTNESS_MIN
+                && brightness <= PowerManager.BRIGHTNESS_MAX;
+    }
+
+    private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+        if (DEBUG) {
+            Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+                    + ", rate=" + rate);
+        }
+        if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
+            Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
+            // TODO(b/153319140) remove when we can get this from the above trace invocation
+            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+            noteScreenBrightness(target);
+        }
+    }
+
+    private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
+        // If there is already an animation in progress, don't interfere with it.
+        if (mColorFadeEnabled
+                && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
+            if (target != Display.STATE_ON) {
+                return;
+            }
+            // If display state changed to on, proceed and stop the color fade and turn screen on.
+            mPendingScreenOff = false;
+        }
+
+        if (mDisplayBlanksAfterDozeConfig
+                && Display.isDozeState(mPowerState.getScreenState())
+                && !Display.isDozeState(target)) {
+            // Skip the screen off animation and add a black surface to hide the
+            // contents of the screen.
+            mPowerState.prepareColorFade(mContext,
+                    mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP);
+            if (mColorFadeOffAnimator != null) {
+                mColorFadeOffAnimator.end();
+            }
+            // Some display hardware will blank itself on the transition between doze and non-doze
+            // but still on display states. In this case we want to report to policy that the
+            // display has turned off so it can prepare the appropriate power on animation, but we
+            // don't want to actually transition to the fully off state since that takes
+            // significantly longer to transition from.
+            setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
+        }
+
+        // If we were in the process of turning off the screen but didn't quite
+        // finish.  Then finish up now to prevent a jarring transition back
+        // to screen on if we skipped blocking screen on as usual.
+        if (mPendingScreenOff && target != Display.STATE_OFF) {
+            setScreenState(Display.STATE_OFF);
+            mPendingScreenOff = false;
+            mPowerState.dismissColorFadeResources();
+        }
+
+        if (target == Display.STATE_ON) {
+            // Want screen on.  The contents of the screen may not yet
+            // be visible if the color fade has not been dismissed because
+            // its last frame of animation is solid black.
+            if (!setScreenState(Display.STATE_ON)) {
+                return; // screen on blocked
+            }
+            if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
+                // Perform screen on animation.
+                if (mPowerState.getColorFadeLevel() == 1.0f) {
+                    mPowerState.dismissColorFade();
+                } else if (mPowerState.prepareColorFade(mContext,
+                        mColorFadeFadesConfig
+                                ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
+                    mColorFadeOnAnimator.start();
+                } else {
+                    mColorFadeOnAnimator.end();
+                }
+            } else {
+                // Skip screen on animation.
+                mPowerState.setColorFadeLevel(1.0f);
+                mPowerState.dismissColorFade();
+            }
+        } else if (target == Display.STATE_VR) {
+            // Wait for brightness animation to complete beforehand when entering VR
+            // from screen on to prevent a perceptible jump because brightness may operate
+            // differently when the display is configured for dozing.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() == Display.STATE_ON) {
+                return;
+            }
+
+            // Set screen state.
+            if (!setScreenState(Display.STATE_VR)) {
+                return; // screen on blocked
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_DOZE) {
+            // Want screen dozing.
+            // Wait for brightness animation to complete beforehand when entering doze
+            // from screen on to prevent a perceptible jump because brightness may operate
+            // differently when the display is configured for dozing.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() == Display.STATE_ON) {
+                return;
+            }
+
+            // Set screen state.
+            if (!setScreenState(Display.STATE_DOZE)) {
+                return; // screen on blocked
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_DOZE_SUSPEND) {
+            // Want screen dozing and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to doze until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+                if (!setScreenState(Display.STATE_DOZE)) {
+                    return; // screen on blocked
+                }
+                setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_ON_SUSPEND) {
+            // Want screen full-power and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to on until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                if (!setScreenState(Display.STATE_ON)) {
+                    return;
+                }
+                setScreenState(Display.STATE_ON_SUSPEND);
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else {
+            // Want screen off.
+            mPendingScreenOff = true;
+            if (!mColorFadeEnabled) {
+                mPowerState.setColorFadeLevel(0.0f);
+            }
+
+            if (mPowerState.getColorFadeLevel() == 0.0f) {
+                // Turn the screen off.
+                // A black surface is already hiding the contents of the screen.
+                setScreenState(Display.STATE_OFF);
+                mPendingScreenOff = false;
+                mPowerState.dismissColorFadeResources();
+            } else if (performScreenOffTransition
+                    && mPowerState.prepareColorFade(mContext,
+                    mColorFadeFadesConfig
+                            ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
+                    && mPowerState.getScreenState() != Display.STATE_OFF) {
+                // Perform the screen off animation.
+                mColorFadeOffAnimator.start();
+            } else {
+                // Skip the screen off animation and add a black surface to hide the
+                // contents of the screen.
+                mColorFadeOffAnimator.end();
+            }
+        }
+    }
+
+    private final Runnable mCleanListener = this::sendUpdatePowerState;
+
+    private void setProximitySensorEnabled(boolean enable) {
+        if (enable) {
+            if (!mProximitySensorEnabled) {
+                // Register the listener.
+                // Proximity sensor state already cleared initially.
+                mProximitySensorEnabled = true;
+                mIgnoreProximityUntilChanged = false;
+                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+            }
+        } else {
+            if (mProximitySensorEnabled) {
+                // Unregister the listener.
+                // Clear the proximity sensor state for next time.
+                mProximitySensorEnabled = false;
+                mProximity = PROXIMITY_UNKNOWN;
+                mIgnoreProximityUntilChanged = false;
+                mPendingProximity = PROXIMITY_UNKNOWN;
+                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mSensorManager.unregisterListener(mProximitySensorListener);
+                // release wake lock(must be last)
+                boolean proxDebounceSuspendBlockerReleased =
+                        mWakelockController.releaseProxDebounceSuspendBlocker();
+                if (proxDebounceSuspendBlockerReleased) {
+                    mPendingProximityDebounceTime = -1;
+                }
+            }
+        }
+    }
+
+    private void handleProximitySensorEvent(long time, boolean positive) {
+        if (mProximitySensorEnabled) {
+            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+                return; // no change
+            }
+            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+                return; // no change
+            }
+
+            // Only accept a proximity sensor reading if it remains
+            // stable for the entire debounce delay.  We hold a wake lock while
+            // debouncing the sensor.
+            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+            if (positive) {
+                mPendingProximity = PROXIMITY_POSITIVE;
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+            } else {
+                mPendingProximity = PROXIMITY_NEGATIVE;
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+            }
+
+            // Debounce the new sensor reading.
+            debounceProximitySensor();
+        }
+    }
+
+    private void debounceProximitySensor() {
+        if (mProximitySensorEnabled
+                && mPendingProximity != PROXIMITY_UNKNOWN
+                && mPendingProximityDebounceTime >= 0) {
+            final long now = mClock.uptimeMillis();
+            if (mPendingProximityDebounceTime <= now) {
+                if (mProximity != mPendingProximity) {
+                    // if the status of the sensor changed, stop ignoring.
+                    mIgnoreProximityUntilChanged = false;
+                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+                }
+                // Sensor reading accepted.  Apply the change then release the wake lock.
+                mProximity = mPendingProximity;
+                updatePowerState();
+                // (must be last)
+                boolean proxDebounceSuspendBlockerReleased =
+                        mWakelockController.releaseProxDebounceSuspendBlocker();
+                if (proxDebounceSuspendBlockerReleased) {
+                    mPendingProximityDebounceTime = -1;
+                }
+
+            } else {
+                // Need to wait a little longer.
+                // Debounce again later.  We continue holding a wake lock while waiting.
+                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+            }
+        }
+    }
+
+    private void sendOnStateChangedWithWakelock() {
+        boolean wakeLockAcquired = mWakelockController.acquireStateChangedSuspendBlocker();
+        if (wakeLockAcquired) {
+            mHandler.post(mWakelockController.getOnStateChangedRunnable());
+        }
+    }
+
+    private void logDisplayPolicyChanged(int newPolicy) {
+        LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY);
+        log.setType(MetricsEvent.TYPE_UPDATE);
+        log.setSubtype(newPolicy);
+        MetricsLogger.action(log);
+    }
+
+    private void handleSettingsChange(boolean userSwitch) {
+        mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        if (userSwitch) {
+            // Don't treat user switches as user initiated change.
+            setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+            updateAutoBrightnessAdjustment();
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.resetShortTermModel();
+            }
+        }
+        // We don't bother with a pending variable for VR screen brightness since we just
+        // immediately adapt to it.
+        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+        sendUpdatePowerState();
+    }
+
+    private float getAutoBrightnessAdjustmentSetting() {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
+    }
+
+    @Override
+    public float getScreenBrightnessSetting() {
+        float brightness = mBrightnessSetting.getBrightness();
+        if (Float.isNaN(brightness)) {
+            brightness = mScreenBrightnessDefault;
+        }
+        return clampAbsoluteBrightness(brightness);
+    }
+
+    private float getScreenBrightnessForVrSetting() {
+        final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
+                UserHandle.USER_CURRENT);
+        return clampScreenBrightnessForVr(brightnessFloat);
+    }
+
+    @Override
+    public void setBrightness(float brightnessValue) {
+        // Update the setting, which will eventually call back into DPC to have us actually update
+        // the display with the new value.
+        mBrightnessSetting.setBrightness(brightnessValue);
+    }
+
+    private void updateScreenBrightnessSetting(float brightnessValue) {
+        if (!isValidBrightnessValue(brightnessValue)
+                || brightnessValue == mCurrentScreenBrightnessSetting) {
+            return;
+        }
+        setCurrentScreenBrightness(brightnessValue);
+        mBrightnessSetting.setBrightness(brightnessValue);
+    }
+
+    private void setCurrentScreenBrightness(float brightnessValue) {
+        if (brightnessValue != mCurrentScreenBrightnessSetting) {
+            mCurrentScreenBrightnessSetting = brightnessValue;
+            postBrightnessChangeRunnable();
+        }
+    }
+
+    private void putAutoBrightnessAdjustmentSetting(float adjustment) {
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mAutoBrightnessAdjustment = adjustment;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
+    private boolean updateAutoBrightnessAdjustment() {
+        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+            return false;
+        }
+        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+            mPendingAutoBrightnessAdjustment = Float.NaN;
+            return false;
+        }
+        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+        mPendingAutoBrightnessAdjustment = Float.NaN;
+        mTemporaryAutoBrightnessAdjustment = Float.NaN;
+        return true;
+    }
+
+    // We want to return true if the user has set the screen brightness.
+    // RBC on, off, and intensity changes will return false.
+    // Slider interactions whilst in RBC will return true, just as when in non-rbc.
+    private boolean updateUserSetScreenBrightness() {
+        if ((Float.isNaN(mPendingScreenBrightnessSetting)
+                || mPendingScreenBrightnessSetting < 0.0f)) {
+            return false;
+        }
+        if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
+            mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            return false;
+        }
+        setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+        mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
+        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        return true;
+    }
+
+    private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
+            boolean hadUserDataPoint) {
+        final float brightnessInNits = convertToNits(brightness);
+        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+                && mAutomaticBrightnessController != null) {
+            // We only want to track changes on devices that can actually map the display backlight
+            // values into a physical brightness unit since the value provided by the API is in
+            // nits and not using the arbitrary backlight units.
+            final float powerFactor = mPowerRequest.lowPowerMode
+                    ? mPowerRequest.screenLowPowerBrightnessFactor
+                    : 1.0f;
+            mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
+                    powerFactor, hadUserDataPoint,
+                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+        }
+    }
+
+    private float convertToNits(float brightness) {
+        if (mAutomaticBrightnessController == null) {
+            return -1f;
+        }
+        return mAutomaticBrightnessController.convertToNits(brightness);
+    }
+
+    @GuardedBy("mLock")
+    private void updatePendingProximityRequestsLocked() {
+        mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+        mPendingWaitForNegativeProximityLocked = false;
+
+        if (mIgnoreProximityUntilChanged) {
+            // Also, lets stop waiting for negative proximity if we're ignoring it.
+            mWaitingForNegativeProximity = false;
+        }
+    }
+
+    private void ignoreProximitySensorUntilChangedInternal() {
+        if (!mIgnoreProximityUntilChanged
+                && mProximity == PROXIMITY_POSITIVE) {
+            // Only ignore if it is still reporting positive (near)
+            mIgnoreProximityUntilChanged = true;
+            Slog.i(mTag, "Ignoring proximity");
+            updatePowerState();
+        }
+    }
+
+    private void sendOnProximityPositiveWithWakelock() {
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+        mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
+    }
+
+
+    private void sendOnProximityNegativeWithWakelock() {
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+        mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
+    }
+
+
+    @Override
+    public void dump(final PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Display Power Controller:");
+            pw.println("  mDisplayId=" + mDisplayId);
+            pw.println("  mLightSensor=" + mLightSensor);
+
+            pw.println();
+            pw.println("Display Power Controller Locked State:");
+            pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
+            pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
+            pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
+            pw.println("  mPendingWaitForNegativeProximityLocked="
+                    + mPendingWaitForNegativeProximityLocked);
+            pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
+        }
+
+        pw.println();
+        pw.println("Display Power Controller Configuration:");
+        pw.println("  mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
+        pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
+        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+        pw.println("  mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
+        pw.println("  mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
+        pw.println("  mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
+        pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
+        pw.println("  mAllowAutoBrightnessWhileDozingConfig="
+                + mAllowAutoBrightnessWhileDozingConfig);
+        pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
+        pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
+        pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
+        synchronized (mCachedBrightnessInfo) {
+            pw.println("  mCachedBrightnessInfo.brightness="
+                    + mCachedBrightnessInfo.brightness.value);
+            pw.println("  mCachedBrightnessInfo.adjustedBrightness="
+                    + mCachedBrightnessInfo.adjustedBrightness.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMin="
+                    + mCachedBrightnessInfo.brightnessMin.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMax="
+                    + mCachedBrightnessInfo.brightnessMax.value);
+            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
+            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint="
+                    + mCachedBrightnessInfo.hbmTransitionPoint.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMaxReason ="
+                    + mCachedBrightnessInfo.brightnessMaxReason.value);
+        }
+        pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
+        pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+
+        mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
+    }
+
+    private void dumpLocal(PrintWriter pw) {
+        pw.println();
+        pw.println("Display Power Controller Thread State:");
+        pw.println("  mPowerRequest=" + mPowerRequest);
+        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+        pw.println("  mProximitySensor=" + mProximitySensor);
+        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
+        pw.println("  mProximityThreshold=" + mProximityThreshold);
+        pw.println("  mProximity=" + proximityToString(mProximity));
+        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
+        pw.println("  mPendingProximityDebounceTime="
+                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+        pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
+        pw.println("  mPendingScreenBrightnessSetting="
+                + mPendingScreenBrightnessSetting);
+        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
+        pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+        pw.println("  mBrightnessReason=" + mBrightnessReason);
+        pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+        pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+        pw.println("  mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
+        pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+        pw.println("  mAppliedDimming=" + mAppliedDimming);
+        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
+        pw.println("  mAppliedThrottling=" + mAppliedThrottling);
+        pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
+        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
+        pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
+                + mAppliedTemporaryAutoBrightnessAdjustment);
+        pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
+        pw.println("  mDozing=" + mDozing);
+        pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
+        pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
+        pw.println("  mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime);
+        pw.println("  mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
+        pw.println("  mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
+        pw.println("  mPendingScreenOff=" + mPendingScreenOff);
+        pw.println("  mReportedToPolicy="
+                + reportedToPolicyToString(mReportedScreenStateToPolicy));
+        pw.println("  mIsRbcActive=" + mIsRbcActive);
+
+        if (mScreenBrightnessRampAnimator != null) {
+            pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
+                    + mScreenBrightnessRampAnimator.isAnimating());
+        }
+
+        if (mColorFadeOnAnimator != null) {
+            pw.println("  mColorFadeOnAnimator.isStarted()="
+                    + mColorFadeOnAnimator.isStarted());
+        }
+        if (mColorFadeOffAnimator != null) {
+            pw.println("  mColorFadeOffAnimator.isStarted()="
+                    + mColorFadeOffAnimator.isStarted());
+        }
+
+        if (mPowerState != null) {
+            mPowerState.dump(pw);
+        }
+
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.dump(pw);
+            dumpBrightnessEvents(pw);
+        }
+
+        if (mHbmController != null) {
+            mHbmController.dump(pw);
+        }
+
+        if (mBrightnessThrottler != null) {
+            mBrightnessThrottler.dump(pw);
+        }
+
+        pw.println();
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.dump(pw);
+            mDisplayWhiteBalanceSettings.dump(pw);
+        }
+
+        pw.println();
+
+        if (mWakelockController != null) {
+            mWakelockController.dumpLocal(pw);
+        }
+    }
+
+    private static String proximityToString(int state) {
+        switch (state) {
+            case PROXIMITY_UNKNOWN:
+                return "Unknown";
+            case PROXIMITY_NEGATIVE:
+                return "Negative";
+            case PROXIMITY_POSITIVE:
+                return "Positive";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private static String reportedToPolicyToString(int state) {
+        switch (state) {
+            case REPORTED_TO_POLICY_SCREEN_OFF:
+                return "REPORTED_TO_POLICY_SCREEN_OFF";
+            case REPORTED_TO_POLICY_SCREEN_TURNING_ON:
+                return "REPORTED_TO_POLICY_SCREEN_TURNING_ON";
+            case REPORTED_TO_POLICY_SCREEN_ON:
+                return "REPORTED_TO_POLICY_SCREEN_ON";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private static String skipRampStateToString(int state) {
+        switch (state) {
+            case RAMP_STATE_SKIP_NONE:
+                return "RAMP_STATE_SKIP_NONE";
+            case RAMP_STATE_SKIP_INITIAL:
+                return "RAMP_STATE_SKIP_INITIAL";
+            case RAMP_STATE_SKIP_AUTOBRIGHT:
+                return "RAMP_STATE_SKIP_AUTOBRIGHT";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private void dumpBrightnessEvents(PrintWriter pw) {
+        int size = mBrightnessEventRingBuffer.size();
+        if (size < 1) {
+            pw.println("No Automatic Brightness Adjustments");
+            return;
+        }
+
+        pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
+        BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray();
+        for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) {
+            pw.println("  " + eventArray[i].toString());
+        }
+    }
+
+    private static float clampAbsoluteBrightness(float value) {
+        return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+    }
+
+    private static float clampAutoBrightnessAdjustment(float value) {
+        return MathUtils.constrain(value, -1.0f, 1.0f);
+    }
+
+    private void noteScreenState(int screenState) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(multi-display): make this multi-display
+                mBatteryStats.noteScreenState(screenState);
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
+    private void noteScreenBrightness(float brightness) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(brightnessfloat): change BatteryStats to use float
+                mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
+                        brightness));
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
+    private void reportStats(float brightness) {
+        if (mLastStatsBrightness == brightness) {
+            return;
+        }
+
+        float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+        synchronized (mCachedBrightnessInfo) {
+            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+                return;
+            }
+            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+        }
+
+        final boolean aboveTransition = brightness > hbmTransitionPoint;
+        final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
+
+        if (aboveTransition || oldAboveTransition) {
+            mLastStatsBrightness = brightness;
+            mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
+            if (aboveTransition != oldAboveTransition) {
+                // report immediately
+                logHbmBrightnessStats(brightness, mDisplayStatsId);
+            } else {
+                // delay for rate limiting
+                Message msg = mHandler.obtainMessage();
+                msg.what = MSG_STATSD_HBM_BRIGHTNESS;
+                msg.arg1 = Float.floatToIntBits(brightness);
+                msg.arg2 = mDisplayStatsId;
+                mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+            }
+        }
+    }
+
+    private void logHbmBrightnessStats(float brightness, int displayStatsId) {
+        synchronized (mHandler) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
+        }
+    }
+
+    private void logManualBrightnessEvent(BrightnessEvent event) {
+        float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+        int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+        float appliedHbmMaxNits =
+                event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+                ? -1f : convertToNits(event.getHbmMax());
+        // thermalCapNits set to -1 if not currently capping max brightness
+        float appliedThermalCapNits =
+                event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+                ? -1f : convertToNits(event.getThermalMax());
+
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                convertToNits(event.getInitialBrightness()),
+                convertToNits(event.getBrightness()),
+                event.getSlowAmbientLux(),
+                event.getPhysicalDisplayId(),
+                event.isShortTermModelActive(),
+                appliedLowPowerMode,
+                appliedRbcStrength,
+                appliedHbmMaxNits,
+                appliedThermalCapNits,
+                event.isAutomaticBrightnessEnabled(),
+                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+    }
+
+    private final class DisplayControllerHandler extends Handler {
+        DisplayControllerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_POWER_STATE:
+                    updatePowerState();
+                    break;
+
+                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+                    debounceProximitySensor();
+                    break;
+
+                case MSG_SCREEN_ON_UNBLOCKED:
+                    if (mPendingScreenOnUnblocker == msg.obj) {
+                        unblockScreenOn();
+                        updatePowerState();
+                    }
+                    break;
+                case MSG_SCREEN_OFF_UNBLOCKED:
+                    if (mPendingScreenOffUnblocker == msg.obj) {
+                        unblockScreenOff();
+                        updatePowerState();
+                    }
+                    break;
+                case MSG_CONFIGURE_BRIGHTNESS:
+                    mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+                    updatePowerState();
+                    break;
+
+                case MSG_SET_TEMPORARY_BRIGHTNESS:
+                    // TODO: Should we have a a timeout for the temporary brightness?
+                    mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+                    updatePowerState();
+                    break;
+
+                case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
+                    mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+                    updatePowerState();
+                    break;
+
+                case MSG_IGNORE_PROXIMITY:
+                    ignoreProximitySensorUntilChangedInternal();
+                    break;
+
+                case MSG_STOP:
+                    cleanupHandlerThreadAfterStop();
+                    break;
+
+                case MSG_UPDATE_BRIGHTNESS:
+                    if (mStopped) {
+                        return;
+                    }
+                    handleSettingsChange(false /*userSwitch*/);
+                    break;
+
+                case MSG_UPDATE_RBC:
+                    handleRbcChanged();
+                    break;
+
+                case MSG_BRIGHTNESS_RAMP_DONE:
+                    if (mPowerState != null) {
+                        final float brightness = mPowerState.getScreenBrightness();
+                        reportStats(brightness);
+                    }
+                    break;
+
+                case MSG_STATSD_HBM_BRIGHTNESS:
+                    logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
+                    break;
+            }
+        }
+    }
+
+    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mProximitySensorEnabled) {
+                final long time = mClock.uptimeMillis();
+                final float distance = event.values[0];
+                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+                handleProximitySensorEvent(time, positive);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            handleSettingsChange(false /* userSwitch */);
+        }
+    }
+
+    private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
+        @Override
+        public void onScreenOn() {
+            Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener {
+        @Override
+        public void onScreenOff() {
+            Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    @Override
+    public void setAutoBrightnessLoggingEnabled(boolean enabled) {
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.setLoggingEnabled(enabled);
+        }
+    }
+
+    @Override // DisplayWhiteBalanceController.Callbacks
+    public void updateWhiteBalance() {
+        sendUpdatePowerState();
+    }
+
+    @Override
+    public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+        }
+    }
+
+    @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();
+        }
+    }
+
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        Clock getClock() {
+            return SystemClock::uptimeMillis;
+        }
+
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+        }
+
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return new DualRampAnimator(dps, firstProperty, secondProperty);
+        }
+
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return new WakelockController(displayId, displayPowerCallbacks);
+        }
+    }
+
+    static class CachedBrightnessInfo {
+        public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat adjustedBrightness =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat brightnessMin =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat brightnessMax =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+        public MutableFloat hbmTransitionPoint =
+                new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+        public MutableInt brightnessMaxReason =
+                new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+
+        public boolean checkAndSetFloat(MutableFloat mf, float f) {
+            if (mf.value != f) {
+                mf.value = f;
+                return true;
+            }
+            return false;
+        }
+
+        public boolean checkAndSetInt(MutableInt mi, int i) {
+            if (mi.value != i) {
+                mi.value = i;
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 3413489..3c522e7 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -29,40 +29,60 @@
 
     private static final boolean DEBUG = false;
 
-    private final float[] mBrighteningThresholds;
-    private final float[] mDarkeningThresholds;
-    private final float[] mThresholdLevels;
+    private final float[] mBrighteningThresholdsPercentages;
+    private final float[] mDarkeningThresholdsPercentages;
+    private final float[] mBrighteningThresholdLevels;
+    private final float[] mDarkeningThresholdLevels;
     private final float mMinDarkening;
     private final float mMinBrightening;
 
     /**
      * Creates a {@code HysteresisLevels} object with the given equal-length
-     * integer arrays.
-     * @param brighteningThresholds an array of brightening hysteresis constraint constants.
-     * @param darkeningThresholds an array of darkening hysteresis constraint constants.
-     * @param thresholdLevels a monotonically increasing array of threshold levels.
+     * float arrays.
+     * @param brighteningThresholdsPercentages 0-100 of thresholds
+     * @param darkeningThresholdsPercentages 0-100 of thresholds
+     * @param brighteningThresholdLevels float array of brightness values in the relevant units
      * @param minBrighteningThreshold the minimum value for which the brightening value needs to
      *                                return.
      * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
+     * @param potentialOldBrightnessRange whether or not the values used could be from the old
+     *                                    screen brightness range ie, between 1-255.
     */
-    HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
-            int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
-        if (brighteningThresholds.length != darkeningThresholds.length
-                || darkeningThresholds.length != thresholdLevels.length + 1) {
+    HysteresisLevels(float[] brighteningThresholdsPercentages,
+            float[] darkeningThresholdsPercentages,
+            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+            float minDarkeningThreshold, float minBrighteningThreshold,
+            boolean potentialOldBrightnessRange) {
+        if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+                || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
             throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
         }
-        mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
-        mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
-        mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
+        mBrighteningThresholdsPercentages =
+                setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+        mDarkeningThresholdsPercentages =
+                setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+        mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+        mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
         mMinDarkening = minDarkeningThreshold;
         mMinBrightening = minBrighteningThreshold;
     }
 
+    HysteresisLevels(float[] brighteningThresholdsPercentages,
+            float[] darkeningThresholdsPercentages,
+            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+            float minDarkeningThreshold, float minBrighteningThreshold) {
+        this(brighteningThresholdsPercentages, darkeningThresholdsPercentages,
+                brighteningThresholdLevels, darkeningThresholdLevels, minDarkeningThreshold,
+                minBrighteningThreshold, false);
+    }
+
     /**
      * Return the brightening hysteresis threshold for the given value level.
      */
     public float getBrighteningThreshold(float value) {
-        final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+        final float brightConstant = getReferenceLevel(value,
+                mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
         float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
             Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
@@ -77,7 +97,8 @@
      * Return the darkening hysteresis threshold for the given value level.
      */
     public float getDarkeningThreshold(float value) {
-        final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+        final float darkConstant = getReferenceLevel(value,
+                mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
         float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
@@ -90,29 +111,38 @@
     /**
      * Return the hysteresis constant for the closest threshold value from the given array.
      */
-    private float getReferenceLevel(float value, float[] referenceLevels) {
-        int index = 0;
-        while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
-            ++index;
+    private float getReferenceLevel(float value, float[] thresholdLevels,
+            float[] thresholdPercentages) {
+        if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+            return 0.0f;
         }
-        return referenceLevels[index];
+        int index = 0;
+        while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+            index++;
+        }
+        return thresholdPercentages[index];
     }
 
     /**
      * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
      */
-    private float[] setArrayFormat(int[] configArray, float divideFactor) {
+    private float[] setArrayFormat(float[] configArray, float divideFactor) {
         float[] levelArray = new float[configArray.length];
         for (int index = 0; levelArray.length > index; ++index) {
-            levelArray[index] = (float)configArray[index] / divideFactor;
+            levelArray[index] = configArray[index] / divideFactor;
         }
         return levelArray;
     }
 
     void dump(PrintWriter pw) {
         pw.println("HysteresisLevels");
-        pw.println("  mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
-        pw.println("  mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
-        pw.println("  mThresholdLevels=" + Arrays.toString(mThresholdLevels));
+        pw.println("  mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
+        pw.println("  mBrighteningThresholdsPercentages="
+                + Arrays.toString(mBrighteningThresholdsPercentages));
+        pw.println("  mMinBrightening=" + mMinBrightening);
+        pw.println("  mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
+        pw.println("  mDarkeningThresholdsPercentages="
+                + Arrays.toString(mDarkeningThresholdsPercentages));
+        pw.println("  mMinDarkening=" + mMinDarkening);
     }
 }
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 66ec5c4..57c2e01 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -7,6 +7,15 @@
                 {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
+        },
+        {
+            "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"}
+            ]
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
new file mode 100644
index 0000000..cbf559f
--- /dev/null
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+/**
+ * A utility class to acquire/release suspend blockers and manage appropriate states around it.
+ * It is also a channel to asynchronously update the PowerManagerService about the changes in the
+ * display states as needed.
+ */
+public final class WakelockController {
+    private static final boolean DEBUG = false;
+
+    // Asynchronous callbacks into the power manager service.
+    // Only invoked from the handler thread while no locks are held.
+    private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
+
+    // Identifiers for suspend blocker acquisition requests
+    private final String mSuspendBlockerIdUnfinishedBusiness;
+    private final String mSuspendBlockerIdOnStateChanged;
+    private final String mSuspendBlockerIdProxPositive;
+    private final String mSuspendBlockerIdProxNegative;
+    private final String mSuspendBlockerIdProxDebounce;
+
+    // True if we have unfinished business and are holding a suspend-blocker.
+    private boolean mUnfinishedBusiness;
+
+    // True if we have have debounced the proximity change impact and are holding a suspend-blocker.
+    private boolean mHasProximityDebounced;
+
+    // The ID of the LogicalDisplay tied to this.
+    private final int mDisplayId;
+    private final String mTag;
+
+    // When true, it implies a wakelock is being held to guarantee the update happens before we
+    // collapse into suspend and so needs to be cleaned up if the thread is exiting.
+    // Should only be accessed on the Handler thread of the class managing the Display states
+    // (i.e. DisplayPowerController2).
+    private boolean mOnStateChangedPending;
+
+    // Count of positive proximity messages currently held. Used to keep track of how many
+    // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
+    // Should only be accessed on the Handler thread of the class managing the Display states
+    // (i.e. DisplayPowerController2).
+    private int mOnProximityPositiveMessages;
+
+    // Count of negative proximity messages currently held. Used to keep track of how many
+    // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
+    // Should only be accessed on the Handler thread of the class managing the Display states
+    // (i.e. DisplayPowerController2).
+    private int mOnProximityNegativeMessages;
+
+    /**
+     * The constructor of WakelockController. Manages the initialization of all the local entities
+     * needed for its appropriate functioning.
+     */
+    public WakelockController(int displayId,
+            DisplayManagerInternal.DisplayPowerCallbacks callbacks) {
+        mDisplayId = displayId;
+        mTag = "WakelockController[" + mDisplayId + "]";
+        mDisplayPowerCallbacks = callbacks;
+        mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business";
+        mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed";
+        mSuspendBlockerIdProxPositive = "[" + displayId + "]prox positive";
+        mSuspendBlockerIdProxNegative = "[" + displayId + "]prox negative";
+        mSuspendBlockerIdProxDebounce = "[" + displayId + "]prox debounce";
+    }
+
+    /**
+     * Acquires the state change wakelock and notifies the PowerManagerService about the changes.
+     */
+    public boolean acquireStateChangedSuspendBlocker() {
+        // Grab a wake lock if we have change of the display state
+        if (!mOnStateChangedPending) {
+            if (DEBUG) {
+                Slog.d(mTag, "State Changed...");
+            }
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            mOnStateChangedPending = true;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Releases the state change wakelock and notifies the PowerManagerService about the changes.
+     */
+    public void releaseStateChangedSuspendBlocker() {
+        if (mOnStateChangedPending) {
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            mOnStateChangedPending = false;
+        }
+    }
+
+    /**
+     * Acquires the unfinished business wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void acquireUnfinishedBusinessSuspendBlocker() {
+        // Grab a wake lock if we have unfinished business.
+        if (!mUnfinishedBusiness) {
+            if (DEBUG) {
+                Slog.d(mTag, "Unfinished business...");
+            }
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+            mUnfinishedBusiness = true;
+        }
+    }
+
+    /**
+     * Releases the unfinished business wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void releaseUnfinishedBusinessSuspendBlocker() {
+        if (mUnfinishedBusiness) {
+            if (DEBUG) {
+                Slog.d(mTag, "Finished business...");
+            }
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+            mUnfinishedBusiness = false;
+        }
+    }
+
+    /**
+     * Acquires the proximity positive wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void acquireProxPositiveSuspendBlocker() {
+        mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
+        mOnProximityPositiveMessages++;
+    }
+
+    /**
+     * Releases the proximity positive wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void releaseProxPositiveSuspendBlocker() {
+        for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+        }
+        mOnProximityPositiveMessages = 0;
+    }
+
+    /**
+     * Acquires the proximity negative wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void acquireProxNegativeSuspendBlocker() {
+        mOnProximityNegativeMessages++;
+        mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+    }
+
+    /**
+     * Releases the proximity negative wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void releaseProxNegativeSuspendBlocker() {
+        for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+        }
+        mOnProximityNegativeMessages = 0;
+    }
+
+    /**
+     * Acquires the proximity debounce wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public void acquireProxDebounceSuspendBlocker() {
+        if (!mHasProximityDebounced) {
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
+        }
+        mHasProximityDebounced = true;
+    }
+
+    /**
+     * Releases the proximity debounce wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    public boolean releaseProxDebounceSuspendBlocker() {
+        if (mHasProximityDebounced) {
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
+            mHasProximityDebounced = false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Gets the Runnable to be executed when the proximity becomes positive.
+     */
+    public Runnable getOnProximityPositiveRunnable() {
+        return () -> {
+            mOnProximityPositiveMessages--;
+            mDisplayPowerCallbacks.onProximityPositive();
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+        };
+    }
+
+    /**
+     * Gets the Runnable to be executed when the display state changes
+     */
+    public Runnable getOnStateChangedRunnable() {
+        return () -> {
+            mOnStateChangedPending = false;
+            mDisplayPowerCallbacks.onStateChanged();
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+        };
+    }
+
+    /**
+     * Gets the Runnable to be executed when the proximity becomes negative.
+     */
+    public Runnable getOnProximityNegativeRunnable() {
+        return () -> {
+            mOnProximityNegativeMessages--;
+            mDisplayPowerCallbacks.onProximityNegative();
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+        };
+    }
+
+    /**
+     * Dumps the current state of this
+     */
+    public void dumpLocal(PrintWriter pw) {
+        pw.println("WakelockController State:");
+        pw.println("  mDisplayId=" + mDisplayId);
+        pw.println("  mUnfinishedBusiness=" + hasUnfinishedBusiness());
+        pw.println("  mOnStateChangePending=" + isOnStateChangedPending());
+        pw.println("  mOnProximityPositiveMessages=" + getOnProximityPositiveMessages());
+        pw.println("  mOnProximityNegativeMessages=" + getOnProximityNegativeMessages());
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerUnfinishedBusinessId() {
+        return mSuspendBlockerIdUnfinishedBusiness;
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerOnStateChangedId() {
+        return mSuspendBlockerIdOnStateChanged;
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerProxPositiveId() {
+        return mSuspendBlockerIdProxPositive;
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerProxNegativeId() {
+        return mSuspendBlockerIdProxNegative;
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerProxDebounceId() {
+        return mSuspendBlockerIdProxDebounce;
+    }
+
+    @VisibleForTesting
+    boolean hasUnfinishedBusiness() {
+        return mUnfinishedBusiness;
+    }
+
+    @VisibleForTesting
+    boolean isOnStateChangedPending() {
+        return mOnStateChangedPending;
+    }
+
+    @VisibleForTesting
+    int getOnProximityPositiveMessages() {
+        return mOnProximityPositiveMessages;
+    }
+
+    @VisibleForTesting
+    int getOnProximityNegativeMessages() {
+        return mOnProximityNegativeMessages;
+    }
+
+    @VisibleForTesting
+    boolean hasProximitySensorDebounced() {
+        return mHasProximityDebounced;
+    }
+}
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index 5b204ad..f6d06aa 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -885,7 +885,7 @@
                     }
                 });
             }
-        } else {
+        } else if (!networkInfo.isConnectedOrConnecting()) {
             mConnectedDeviceGroupInfo = null;
 
             // Disconnect if we lost the network while connecting or connected to a display.
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fee1f5c..e1b18f2 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -38,7 +38,6 @@
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -66,6 +65,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.ActivityInterceptorCallback;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 86732a1..696b604 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.BatteryState;
 import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.IBinder;
@@ -47,14 +48,22 @@
 /**
  * A thread-safe component of {@link InputManagerService} responsible for managing the battery state
  * of input devices.
+ *
+ * Interactions with BatteryController can happen on several threads, including Binder threads, the
+ * {@link UEventObserver}'s thread, or its own Handler thread, among others. All public methods, and
+ * private methods prefixed with "handle-" (e.g. {@link #handleListeningProcessDied(int)}),
+ * serve as entry points for these threads.
  */
-final class BatteryController implements InputManager.InputDeviceListener {
+final class BatteryController {
     private static final String TAG = BatteryController.class.getSimpleName();
 
     // To enable these logs, run:
     // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    @VisibleForTesting
+    static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds
+
     private final Object mLock = new Object();
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -66,10 +75,14 @@
     @GuardedBy("mLock")
     private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
 
-    // Maps a deviceId that is being monitored to the battery state for the device.
-    // This must be kept in sync with {@link #mListenerRecords}.
+    // Maps a deviceId that is being monitored to the monitor for the battery state of the device.
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>();
+    private final ArrayMap<Integer, DeviceMonitor> mDeviceMonitors = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    private boolean mIsPolling = false;
+    @GuardedBy("mLock")
+    private boolean mIsInteractive = true;
 
     BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
         this(context, nativeService, looper, new UEventManager() {});
@@ -84,9 +97,9 @@
         mUEventManager = uEventManager;
     }
 
-    void systemRunning() {
+    public void systemRunning() {
         Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                .registerInputDeviceListener(this, mHandler);
+                .registerInputDeviceListener(mInputDeviceListener, mHandler);
     }
 
     /**
@@ -94,7 +107,7 @@
      * state.
      */
     @BinderThread
-    void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+    public void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
             int pid) {
         synchronized (mLock) {
             ListenerRecord listenerRecord = mListenerRecords.get(pid);
@@ -123,11 +136,11 @@
                                 + " is already monitoring deviceId " + deviceId);
             }
 
-            MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
-            if (deviceState == null) {
+            DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+            if (monitor == null) {
                 // This is the first listener that is monitoring this device.
-                deviceState = new MonitoredDeviceState(deviceId);
-                mMonitoredDeviceStates.put(deviceId, deviceState);
+                monitor = new DeviceMonitor(deviceId);
+                mDeviceMonitors.put(deviceId, monitor);
             }
 
             if (DEBUG) {
@@ -135,33 +148,50 @@
                         + " is monitoring deviceId " + deviceId);
             }
 
-            notifyBatteryListener(listenerRecord, deviceState);
+            updatePollingLocked(true /*delayStart*/);
+            notifyBatteryListener(listenerRecord, monitor.getBatteryStateForReporting());
         }
     }
 
-    private static void notifyBatteryListener(ListenerRecord listenerRecord,
-            MonitoredDeviceState deviceState) {
+    private static void notifyBatteryListener(ListenerRecord listenerRecord, State state) {
         try {
-            listenerRecord.mListener.onBatteryStateChanged(
-                    deviceState.mDeviceId,
-                    deviceState.mHasBattery,
-                    deviceState.mBatteryStatus,
-                    deviceState.mBatteryCapacity,
-                    deviceState.mLastUpdateTime);
+            listenerRecord.mListener.onBatteryStateChanged(state);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to notify listener", e);
         }
+        if (DEBUG) {
+            Slog.d(TAG, "Notified battery listener from pid " + listenerRecord.mPid
+                    + " of state of deviceId " + state.deviceId);
+        }
     }
 
     @GuardedBy("mLock")
-    private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) {
+    private void notifyAllListenersForDeviceLocked(State state) {
+        if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state);
         mListenerRecords.forEach((pid, listenerRecord) -> {
-            if (listenerRecord.mMonitoredDevices.contains(deviceState.mDeviceId)) {
-                notifyBatteryListener(listenerRecord, deviceState);
+            if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) {
+                notifyBatteryListener(listenerRecord, state);
             }
         });
     }
 
+    @GuardedBy("mLock")
+    private void updatePollingLocked(boolean delayStart) {
+        if (mDeviceMonitors.isEmpty() || !mIsInteractive) {
+            // Stop polling.
+            mIsPolling = false;
+            mHandler.removeCallbacks(this::handlePollEvent);
+            return;
+        }
+
+        if (mIsPolling) {
+            return;
+        }
+        // Start polling.
+        mIsPolling = true;
+        mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
+    }
+
     private boolean hasBattery(int deviceId) {
         final InputDevice device =
                 Objects.requireNonNull(mContext.getSystemService(InputManager.class))
@@ -170,8 +200,8 @@
     }
 
     @GuardedBy("mLock")
-    private MonitoredDeviceState getDeviceStateOrThrowLocked(int deviceId) {
-        return Objects.requireNonNull(mMonitoredDeviceStates.get(deviceId),
+    private DeviceMonitor getDeviceMonitorOrThrowLocked(int deviceId) {
+        return Objects.requireNonNull(mDeviceMonitors.get(deviceId),
                 "Maps are out of sync: Cannot find device state for deviceId " + deviceId);
     }
 
@@ -181,8 +211,8 @@
      * removed.
      */
     @BinderThread
-    void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
-            int pid) {
+    public void unregisterBatteryListener(int deviceId,
+            @NonNull IInputDeviceBatteryListener listener, int pid) {
         synchronized (mLock) {
             final ListenerRecord listenerRecord = mListenerRecords.get(pid);
             if (listenerRecord == null) {
@@ -221,9 +251,9 @@
 
         if (!hasRegisteredListenerForDeviceLocked(deviceId)) {
             // There are no more listeners monitoring this device.
-            final MonitoredDeviceState deviceState = getDeviceStateOrThrowLocked(deviceId);
-            deviceState.stopMonitoring();
-            mMonitoredDeviceStates.remove(deviceId);
+            final DeviceMonitor monitor = getDeviceMonitorOrThrowLocked(deviceId);
+            monitor.stopMonitoring();
+            mDeviceMonitors.remove(deviceId);
         }
 
         if (listenerRecord.mMonitoredDevices.isEmpty()) {
@@ -232,6 +262,8 @@
             mListenerRecords.remove(pid);
             if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
         }
+
+        updatePollingLocked(false /*delayStart*/);
     }
 
     @GuardedBy("mLock")
@@ -260,66 +292,120 @@
         }
     }
 
-    // Query the battery state for the device and notify all listeners if there is a change.
-    private void handleBatteryChangeNotification(int deviceId, long eventTime) {
+    private void handleUEventNotification(int deviceId, long eventTime) {
         synchronized (mLock) {
-            final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
-            if (deviceState == null) {
+            final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+            if (monitor == null) {
                 return;
             }
-            if (deviceState.updateBatteryState(eventTime)) {
-                notifyAllListenersForDeviceLocked(deviceState);
+            if (monitor.updateBatteryState(eventTime)) {
+                notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
             }
         }
     }
 
-    void dump(PrintWriter pw, String prefix) {
+    private void handlePollEvent() {
         synchronized (mLock) {
-            pw.println(prefix + TAG + ": " + mListenerRecords.size()
-                    + " battery listeners");
+            if (!mIsPolling) {
+                return;
+            }
+            final long eventTime = SystemClock.uptimeMillis();
+            mDeviceMonitors.forEach((deviceId, monitor) -> {
+                // Re-acquire lock in the lambda to silence error-prone build warnings.
+                synchronized (mLock) {
+                    if (monitor.updateBatteryState(eventTime)) {
+                        notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
+                    }
+                }
+            });
+            mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS);
+        }
+    }
+
+    /** Gets the current battery state of an input device. */
+    public IInputDeviceBatteryState getBatteryState(int deviceId) {
+        synchronized (mLock) {
+            final long updateTime = SystemClock.uptimeMillis();
+            final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+            if (monitor == null) {
+                // The input device's battery is not being monitored by any listener.
+                return queryBatteryStateFromNative(deviceId, updateTime);
+            }
+            // Force the battery state to update, and notify listeners if necessary.
+            final boolean stateChanged = monitor.updateBatteryState(updateTime);
+            final State state = monitor.getBatteryStateForReporting();
+            if (stateChanged) {
+                notifyAllListenersForDeviceLocked(state);
+            }
+            return state;
+        }
+    }
+
+    public void onInteractiveChanged(boolean interactive) {
+        synchronized (mLock) {
+            mIsInteractive = interactive;
+            updatePollingLocked(false /*delayStart*/);
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            final String indent = prefix + "  ";
+            final String indent2 = indent + "  ";
+
+            pw.println(prefix + TAG + ":");
+            pw.println(indent + "State: Polling = " + mIsPolling
+                    + ", Interactive = " + mIsInteractive);
+
+            pw.println(indent + "Listeners: " + mListenerRecords.size() + " battery listeners");
             for (int i = 0; i < mListenerRecords.size(); i++) {
-                pw.println(prefix + "  " + i + ": " + mListenerRecords.valueAt(i));
+                pw.println(indent2 + i + ": " + mListenerRecords.valueAt(i));
+            }
+
+            pw.println(indent + "Device Monitors: " + mDeviceMonitors.size() + " monitors");
+            for (int i = 0; i < mDeviceMonitors.size(); i++) {
+                pw.println(indent2 + i + ": " + mDeviceMonitors.valueAt(i));
             }
         }
     }
 
     @SuppressWarnings("all")
-    void monitor() {
+    public void monitor() {
         synchronized (mLock) {
             return;
         }
     }
 
-    @VisibleForTesting
-    @Override
-    public void onInputDeviceAdded(int deviceId) {}
+    private final InputManager.InputDeviceListener mInputDeviceListener =
+            new InputManager.InputDeviceListener() {
+        @Override
+        public void onInputDeviceAdded(int deviceId) {}
 
-    @VisibleForTesting
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {}
+        @Override
+        public void onInputDeviceRemoved(int deviceId) {}
 
-    @VisibleForTesting
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
-        synchronized (mLock) {
-            final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
-            if (deviceState == null) {
-                return;
-            }
-            final long eventTime = SystemClock.uptimeMillis();
-            if (deviceState.updateBatteryState(eventTime)) {
-                notifyAllListenersForDeviceLocked(deviceState);
+        @Override
+        public void onInputDeviceChanged(int deviceId) {
+            synchronized (mLock) {
+                final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+                if (monitor == null) {
+                    return;
+                }
+                final long eventTime = SystemClock.uptimeMillis();
+                if (monitor.updateBatteryState(eventTime)) {
+                    notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
+                }
             }
         }
-    }
+    };
 
     // A record of a registered battery listener from one process.
     private class ListenerRecord {
-        final int mPid;
-        final IInputDeviceBatteryListener mListener;
-        final IBinder.DeathRecipient mDeathRecipient;
+        public final int mPid;
+        public final IInputDeviceBatteryListener mListener;
+        public final IBinder.DeathRecipient mDeathRecipient;
         // The set of deviceIds that are currently being monitored by this listener.
-        final Set<Integer> mMonitoredDevices;
+        public final Set<Integer> mMonitoredDevices;
 
         ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
             mPid = pid;
@@ -335,21 +421,27 @@
         }
     }
 
-    // Holds the state of an InputDevice for which battery changes are currently being monitored.
-    private class MonitoredDeviceState {
-        private final int mDeviceId;
+    // Queries the battery state of an input device from native code.
+    private State queryBatteryStateFromNative(int deviceId, long updateTime) {
+        final boolean isPresent = hasBattery(deviceId);
+        return new State(
+                deviceId,
+                updateTime,
+                isPresent,
+                isPresent ? mNative.getBatteryStatus(deviceId) : BatteryState.STATUS_UNKNOWN,
+                isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN);
+    }
 
-        private long mLastUpdateTime = 0;
-        private boolean mHasBattery = false;
-        @BatteryState.BatteryStatus
-        private int mBatteryStatus = BatteryState.STATUS_UNKNOWN;
-        private float mBatteryCapacity = Float.NaN;
+    // Holds the state of an InputDevice for which battery changes are currently being monitored.
+    private class DeviceMonitor {
+        @NonNull
+        private State mState;
 
         @Nullable
         private UEventListener mUEventListener;
 
-        MonitoredDeviceState(int deviceId) {
-            mDeviceId = deviceId;
+        DeviceMonitor(int deviceId) {
+            mState = new State(deviceId);
 
             // Load the initial battery state and start monitoring.
             final long eventTime = SystemClock.uptimeMillis();
@@ -357,56 +449,57 @@
         }
 
         // Returns true if the battery state changed since the last time it was updated.
-        boolean updateBatteryState(long eventTime) {
-            mLastUpdateTime = eventTime;
+        public boolean updateBatteryState(long updateTime) {
+            mState.updateTime = updateTime;
 
-            final boolean batteryPresenceChanged = mHasBattery != hasBattery(mDeviceId);
-            if (batteryPresenceChanged) {
-                mHasBattery = !mHasBattery;
-                if (mHasBattery) {
+            final State updatedState = queryBatteryStateFromNative(mState.deviceId, updateTime);
+            if (mState.equals(updatedState)) {
+                return false;
+            }
+            if (mState.isPresent != updatedState.isPresent) {
+                if (updatedState.isPresent) {
                     startMonitoring();
                 } else {
                     stopMonitoring();
                 }
             }
-
-            final int oldStatus = mBatteryStatus;
-            final float oldCapacity = mBatteryCapacity;
-
-            if (mHasBattery) {
-                mBatteryStatus = mNative.getBatteryStatus(mDeviceId);
-                mBatteryCapacity = mNative.getBatteryCapacity(mDeviceId) / 100.f;
-            } else {
-                mBatteryStatus = BatteryState.STATUS_UNKNOWN;
-                mBatteryCapacity = Float.NaN;
-            }
-
-            return batteryPresenceChanged
-                    || mBatteryStatus != oldStatus
-                    || mBatteryCapacity != oldCapacity;
+            mState = updatedState;
+            return true;
         }
 
         private void startMonitoring() {
-            final String batteryPath = mNative.getBatteryDevicePath(mDeviceId);
+            final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId);
             if (batteryPath == null) {
                 return;
             }
+            final int deviceId = mState.deviceId;
             mUEventListener = new UEventListener() {
                 @Override
-                void onUEvent(long eventTime) {
-                    handleBatteryChangeNotification(mDeviceId, eventTime);
+                public void onUEvent(long eventTime) {
+                    handleUEventNotification(deviceId, eventTime);
                 }
             };
             mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
         }
 
         // This must be called when the device is no longer being monitored.
-        void stopMonitoring() {
+        public void stopMonitoring() {
             if (mUEventListener != null) {
                 mUEventManager.removeListener(mUEventListener);
                 mUEventListener = null;
             }
         }
+
+        // Returns the current battery state that can be used to notify listeners BatteryController.
+        public State getBatteryStateForReporting() {
+            return new State(mState);
+        }
+
+        @Override
+        public String toString() {
+            return "state=" + mState
+                    + ", uEventListener=" + (mUEventListener != null ? "added" : "none");
+        }
     }
 
     // An interface used to change the API of UEventObserver to a more test-friendly format.
@@ -428,7 +521,7 @@
                 }
             };
 
-            abstract void onUEvent(long eventTime);
+            public abstract void onUEvent(long eventTime);
         }
 
         default void addListener(UEventListener listener, String match) {
@@ -439,4 +532,37 @@
             listener.mObserver.stopObserving();
         }
     }
+
+    // Helper class that adds copying and printing functionality to IInputDeviceBatteryState.
+    private static class State extends IInputDeviceBatteryState {
+
+        State(int deviceId) {
+            initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN,
+                    Float.NaN /*capacity*/);
+        }
+
+        State(IInputDeviceBatteryState s) {
+            initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity);
+        }
+
+        State(int deviceId, long updateTime, boolean isPresent, int status, float capacity) {
+            initialize(deviceId, updateTime, isPresent, status, capacity);
+        }
+
+        private void initialize(int deviceId, long updateTime, boolean isPresent, int status,
+                float capacity) {
+            this.deviceId = deviceId;
+            this.updateTime = updateTime;
+            this.isPresent = isPresent;
+            this.status = status;
+            this.capacity = capacity;
+        }
+
+        @Override
+        public String toString() {
+            return "BatteryState{deviceId=" + deviceId + ", updateTime=" + updateTime
+                    + ", isPresent=" + isPresent + ", status=" + status + ", capacity=" + capacity
+                    + " }";
+        }
+    }
 }
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
similarity index 92%
rename from core/java/android/hardware/input/InputManagerInternal.java
rename to services/core/java/com/android/server/input/InputManagerInternal.java
index fc6bc55..7eb5a10 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.input;
+package com.android.server.input;
 
 import android.annotation.NonNull;
 import android.graphics.PointF;
@@ -140,4 +140,16 @@
      * canceled for all other channels.
      */
     public abstract void pilferPointers(IBinder token);
+
+    /**
+     * Increments keyboard backlight level if the device has an associated keyboard backlight
+     * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
+     */
+    public abstract void incrementKeyboardBacklight(int deviceId);
+
+    /**
+     * Decrements keyboard backlight level if the device has an associated keyboard backlight
+     * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
+     */
+    public abstract void decrementKeyboardBacklight(int deviceId);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4da0c0d..69b0e65 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -49,14 +49,13 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
-import android.hardware.input.InputManagerInternal.LidSwitchCallback;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.TouchCalibration;
@@ -123,6 +122,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
+import com.android.server.input.InputManagerInternal.LidSwitchCallback;
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.IoUtils;
@@ -312,6 +312,9 @@
     // Manages battery state for input devices.
     private final BatteryController mBatteryController;
 
+    // Manages Keyboard backlight
+    private final KeyboardBacklightController mKeyboardBacklightController;
+
     // Maximum number of milliseconds to wait for input event injection.
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -422,6 +425,8 @@
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
+        mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
+                mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -563,6 +568,7 @@
         }
 
         mBatteryController.systemRunning();
+        mKeyboardBacklightController.systemRunning();
     }
 
     private void reloadKeyboardLayouts() {
@@ -2306,14 +2312,8 @@
 
     // Binder call
     @Override
-    public int getBatteryStatus(int deviceId) {
-        return mNative.getBatteryStatus(deviceId);
-    }
-
-    // Binder call
-    @Override
-    public int getBatteryCapacity(int deviceId) {
-        return mNative.getBatteryCapacity(deviceId);
+    public IInputDeviceBatteryState getBatteryState(int deviceId) {
+        return mBatteryController.getBatteryState(deviceId);
     }
 
     // Binder call
@@ -2686,6 +2686,7 @@
         dumpSpyWindowGestureMonitors(pw, "  " /*prefix*/);
         dumpDisplayInputPropertiesValues(pw, "  " /*prefix*/);
         mBatteryController.dump(pw, "  " /*prefix*/);
+        mKeyboardBacklightController.dump(pw, "  " /*prefix*/);
     }
 
     private void dumpAssociations(PrintWriter pw, String prefix) {
@@ -3684,6 +3685,7 @@
         @Override
         public void setInteractive(boolean interactive) {
             mNative.setInteractive(interactive);
+            mBatteryController.onInteractiveChanged(interactive);
         }
 
         @Override
@@ -3762,6 +3764,16 @@
         public void pilferPointers(IBinder token) {
             mNative.pilferPointers(token);
         }
+
+        @Override
+        public void incrementKeyboardBacklight(int deviceId) {
+            mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
+        }
+
+        @Override
+        public void decrementKeyboardBacklight(int deviceId) {
+            mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
new file mode 100644
index 0000000..e33f28c
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.input.InputManager;
+import android.hardware.lights.Light;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.OptionalInt;
+import java.util.TreeSet;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
+ * backlight for supported keyboards.
+ */
+final class KeyboardBacklightController implements InputManager.InputDeviceListener {
+
+    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);
+
+    private enum Direction {
+        DIRECTION_UP, DIRECTION_DOWN
+    }
+    private static final int MSG_INCREMENT_KEYBOARD_BACKLIGHT = 1;
+    private static final int MSG_DECREMENT_KEYBOARD_BACKLIGHT = 2;
+    private static final int MAX_BRIGHTNESS = 255;
+    private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+    @VisibleForTesting
+    static final TreeSet<Integer> BRIGHTNESS_LEVELS = new TreeSet<>();
+
+    private final Context mContext;
+    private final NativeInputManagerService mNative;
+    // The PersistentDataStore should be locked before use.
+    @GuardedBy("mDataStore")
+    private final PersistentDataStore mDataStore;
+    private final Handler mHandler;
+    private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>();
+
+    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_LEVELS.add(
+                    (int) Math.floor(((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS));
+        }
+    }
+
+    KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper) {
+        mContext = context;
+        mNative = nativeService;
+        mDataStore = dataStore;
+        mHandler = new Handler(looper, this::handleMessage);
+    }
+
+    void systemRunning() {
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        inputManager.registerInputDeviceListener(this, mHandler);
+        // Circle through all the already added input devices
+        for (int deviceId : inputManager.getInputDeviceIds()) {
+            onInputDeviceAdded(deviceId);
+        }
+    }
+
+    public void incrementKeyboardBacklight(int deviceId) {
+        Message msg = Message.obtain(mHandler, MSG_INCREMENT_KEYBOARD_BACKLIGHT, deviceId);
+        mHandler.sendMessage(msg);
+    }
+
+    public void decrementKeyboardBacklight(int deviceId) {
+        Message msg = Message.obtain(mHandler, MSG_DECREMENT_KEYBOARD_BACKLIGHT, deviceId);
+        mHandler.sendMessage(msg);
+    }
+
+    private void updateKeyboardBacklight(int deviceId, Direction direction) {
+        InputDevice inputDevice = getInputDevice(deviceId);
+        Light keyboardBacklight = mKeyboardBacklights.get(deviceId);
+        if (inputDevice == null || keyboardBacklight == null) {
+            return;
+        }
+        // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
+        int currBrightness = BRIGHTNESS_LEVELS.floor(Color.alpha(
+                mNative.getLightColor(deviceId, keyboardBacklight.getId())));
+        int newBrightness;
+        if (direction == Direction.DIRECTION_UP) {
+            newBrightness = currBrightness != MAX_BRIGHTNESS ? BRIGHTNESS_LEVELS.higher(
+                    currBrightness) : currBrightness;
+        } else {
+            newBrightness = currBrightness != 0 ? BRIGHTNESS_LEVELS.lower(currBrightness)
+                    : currBrightness;
+        }
+        @ColorInt int newColor = Color.argb(newBrightness, 0, 0, 0);
+        mNative.setLightColor(deviceId, keyboardBacklight.getId(), newColor);
+        if (DEBUG) {
+            Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness);
+        }
+
+        synchronized (mDataStore) {
+            try {
+                mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
+                        keyboardBacklight.getId(),
+                        newBrightness);
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+        OptionalInt brightness;
+        synchronized (mDataStore) {
+            brightness = mDataStore.getKeyboardBacklightBrightness(
+                    inputDevice.getDescriptor(), keyboardBacklight.getId());
+        }
+        if (!brightness.isEmpty()) {
+            mNative.setLightColor(inputDevice.getId(), keyboardBacklight.getId(),
+                    Color.argb(brightness.getAsInt(), 0, 0, 0));
+            if (DEBUG) {
+                Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
+            }
+        }
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_INCREMENT_KEYBOARD_BACKLIGHT:
+                updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_UP);
+                return true;
+            case MSG_DECREMENT_KEYBOARD_BACKLIGHT:
+                updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_DOWN);
+                return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        onInputDeviceChanged(deviceId);
+    }
+
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        mKeyboardBacklights.remove(deviceId);
+    }
+
+    @VisibleForTesting
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        InputDevice inputDevice = getInputDevice(deviceId);
+        if (inputDevice == null) {
+            return;
+        }
+        final Light keyboardBacklight = getKeyboardBacklight(inputDevice);
+        if (keyboardBacklight == null) {
+            mKeyboardBacklights.remove(deviceId);
+            return;
+        }
+        final Light oldBacklight = mKeyboardBacklights.get(deviceId);
+        if (oldBacklight != null && oldBacklight.getId() == keyboardBacklight.getId()) {
+            return;
+        }
+        // The keyboard backlight was added or changed.
+        mKeyboardBacklights.put(deviceId, keyboardBacklight);
+        restoreBacklightBrightness(inputDevice, keyboardBacklight);
+    }
+
+    private InputDevice getInputDevice(int deviceId) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
+    }
+
+    private Light getKeyboardBacklight(InputDevice inputDevice) {
+        // Assuming each keyboard can have only single Light node for Keyboard backlight control
+        // for simplicity.
+        for (Light light : inputDevice.getLightsManager().getLights()) {
+            if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT
+                    && light.hasBrightnessControl()) {
+                return light;
+            }
+        }
+        return null;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            Light light = mKeyboardBacklights.get(i);
+            pw.println(prefix + "  " + i + ": { id: " + light.getId() + ", name: " + light.getName()
+                    + " }");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 6cec272..7dce28c 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -16,40 +16,37 @@
 
 package com.android.server.input;
 
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 import android.annotation.Nullable;
-import android.view.Surface;
 import android.hardware.input.TouchCalibration;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
+import android.view.Surface;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.OptionalInt;
 import java.util.Set;
 
-import libcore.io.IoUtils;
-
 /**
  * Manages persistent state recorded by the input manager service as an XML file.
  * Caller must acquire lock on the data store before accessing it.
@@ -69,7 +66,9 @@
     // Input device state by descriptor.
     private final HashMap<String, InputDeviceState> mInputDevices =
             new HashMap<String, InputDeviceState>();
-    private final AtomicFile mAtomicFile;
+
+    // The interface for methods which should be replaced by the test harness.
+    private Injector mInjector;
 
     // True if the data has been loaded.
     private boolean mLoaded;
@@ -78,8 +77,12 @@
     private boolean mDirty;
 
     public PersistentDataStore() {
-        mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"),
-                "input-state");
+        this(new Injector());
+    }
+
+    @VisibleForTesting
+    PersistentDataStore(Injector injector) {
+        mInjector = injector;
     }
 
     public void saveIfNeeded() {
@@ -90,7 +93,7 @@
     }
 
     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
             return TouchCalibration.IDENTITY;
         }
@@ -103,7 +106,7 @@
     }
 
     public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
 
         if (state.setTouchCalibration(surfaceRotation, calibration)) {
             setDirty();
@@ -114,13 +117,13 @@
     }
 
     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         return state != null ? state.getCurrentKeyboardLayout() : null;
     }
 
     public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
         if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
             setDirty();
             return true;
@@ -129,7 +132,7 @@
     }
 
     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
             return (String[])ArrayUtils.emptyArray(String.class);
         }
@@ -138,7 +141,7 @@
 
     public boolean addKeyboardLayout(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
             setDirty();
             return true;
@@ -148,7 +151,7 @@
 
     public boolean removeKeyboardLayout(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
         if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
             setDirty();
             return true;
@@ -157,7 +160,7 @@
     }
 
     public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
-        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state != null && state.switchKeyboardLayout(direction)) {
             setDirty();
             return true;
@@ -165,6 +168,24 @@
         return false;
     }
 
+    public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
+            int brightness) {
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
+        if (state.setKeyboardBacklightBrightness(lightId, brightness)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public OptionalInt getKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
+        if (state == null) {
+            return OptionalInt.empty();
+        }
+        return state.getKeyboardBacklightBrightness(lightId);
+    }
+
     public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
         boolean changed = false;
         for (InputDeviceState state : mInputDevices.values()) {
@@ -179,11 +200,15 @@
         return false;
     }
 
-    private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
-            boolean createIfAbsent) {
+    private InputDeviceState getInputDeviceState(String inputDeviceDescriptor) {
+        loadIfNeeded();
+        return mInputDevices.get(inputDeviceDescriptor);
+    }
+
+    private InputDeviceState getOrCreateInputDeviceState(String inputDeviceDescriptor) {
         loadIfNeeded();
         InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
-        if (state == null && createIfAbsent) {
+        if (state == null) {
             state = new InputDeviceState();
             mInputDevices.put(inputDeviceDescriptor, state);
             setDirty();
@@ -211,7 +236,7 @@
 
         final InputStream is;
         try {
-            is = mAtomicFile.openRead();
+            is = mInjector.openRead();
         } catch (FileNotFoundException ex) {
             return;
         }
@@ -234,7 +259,7 @@
     private void save() {
         final FileOutputStream os;
         try {
-            os = mAtomicFile.startWrite();
+            os = mInjector.startWrite();
             boolean success = false;
             try {
                 TypedXmlSerializer serializer = Xml.resolveSerializer(os);
@@ -242,11 +267,7 @@
                 serializer.flush();
                 success = true;
             } finally {
-                if (success) {
-                    mAtomicFile.finishWrite(os);
-                } else {
-                    mAtomicFile.failWrite(os);
-                }
+                mInjector.finishWrite(os, success);
             }
         } catch (IOException ex) {
             Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
@@ -307,10 +328,12 @@
         private static final String[] CALIBRATION_NAME = { "x_scale",
                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
 
-        private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
+        private static final int INVALID_VALUE = -1;
+        private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
         @Nullable
         private String mCurrentKeyboardLayout;
-        private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+        private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+        private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
 
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
@@ -377,6 +400,19 @@
             return true;
         }
 
+        public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
+            if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
+                return false;
+            }
+            mKeyboardBacklightBrightnessMap.put(lightId, brightness);
+            return true;
+        }
+
+        public OptionalInt getKeyboardBacklightBrightness(int lightId) {
+            int brightness = mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE);
+            return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
+        }
+
         private void updateCurrentKeyboardLayoutIfRemoved(
                 String removedKeyboardLayout, int removedIndex) {
             if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
@@ -446,6 +482,10 @@
                         }
                         mCurrentKeyboardLayout = descriptor;
                     }
+                } else if (parser.getName().equals("light-info")) {
+                    int lightId = parser.getAttributeInt(null, "light-id");
+                    int lightBrightness = parser.getAttributeInt(null, "light-brightness");
+                    mKeyboardBacklightBrightnessMap.put(lightId, lightBrightness);
                 } else if (parser.getName().equals("calibration")) {
                     String format = parser.getAttributeValue(null, "format");
                     String rotation = parser.getAttributeValue(null, "rotation");
@@ -515,6 +555,15 @@
                 serializer.endTag(null, "keyboard-layout");
             }
 
+            for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
+                int lightId = mKeyboardBacklightBrightnessMap.valueAt(i);
+                serializer.startTag(null, "light-info");
+                serializer.attributeInt(null, "light-id", lightId);
+                serializer.attributeInt(null, "light-brightness",
+                        mKeyboardBacklightBrightnessMap.get(lightId));
+                serializer.endTag(null, "light-info");
+            }
+
             for (int i = 0; i < mTouchCalibration.length; i++) {
                 if (mTouchCalibration[i] != null) {
                     String rotation = surfaceRotationToString(i);
@@ -559,4 +608,30 @@
             throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'");
         }
     }
+
+    @VisibleForTesting
+    static class Injector {
+        private final AtomicFile mAtomicFile;
+
+        Injector() {
+            mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"),
+                    "input-state");
+        }
+
+        InputStream openRead() throws FileNotFoundException {
+            return mAtomicFile.openRead();
+        }
+
+        FileOutputStream startWrite() throws IOException {
+            return mAtomicFile.startWrite();
+        }
+
+        void finishWrite(FileOutputStream fos, boolean success) {
+            if (success) {
+                mAtomicFile.finishWrite(fos);
+            } else {
+                mAtomicFile.failWrite(fos);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index f89b6ae..a4830be 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -21,7 +21,6 @@
 import android.annotation.AnyThread;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
-import android.hardware.input.InputManagerInternal;
 import android.os.IBinder;
 import android.os.Looper;
 import android.util.Slog;
@@ -35,6 +34,7 @@
 import android.view.SurfaceControl;
 
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 3e39746..82436cc 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -221,14 +221,12 @@
     }
 
     @AnyThread
-    boolean updateEditorToolType(int toolType) {
+    void updateEditorToolType(@MotionEvent.ToolType int toolType) {
         try {
             mTarget.updateEditorToolType(toolType);
         } catch (RemoteException e) {
             logRemoteException(e);
-            return false;
         }
-        return true;
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
index 5a0069a..789222e 100644
--- a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
+++ b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
@@ -1,18 +1,18 @@
 /*
-** Copyright 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.
-*/
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 package com.android.server.inputmethod;
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 23ea39a..6dbb362 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -26,12 +26,10 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManagerInternal;
-import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -39,7 +37,6 @@
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
-import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
@@ -66,9 +63,7 @@
     @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
     @NonNull private final InputMethodUtils.InputMethodSettings mSettings;
     @NonNull private final PackageManagerInternal mPackageManagerInternal;
-    @NonNull private final IWindowManager mIWindowManager;
     @NonNull private final WindowManagerInternal mWindowManagerInternal;
-    @NonNull private final Resources mRes;
 
     @GuardedBy("ImfLock.class") private long mLastBindTime;
     @GuardedBy("ImfLock.class") private boolean mHasConnection;
@@ -80,7 +75,7 @@
     @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
     @GuardedBy("ImfLock.class") private int mCurSeq;
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
-    private boolean mSupportsStylusHw;
+    @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
 
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
@@ -107,9 +102,7 @@
         mMethodMap = mService.mMethodMap;
         mSettings = mService.mSettings;
         mPackageManagerInternal = mService.mPackageManagerInternal;
-        mIWindowManager = mService.mIWindowManager;
         mWindowManagerInternal = mService.mWindowManagerInternal;
-        mRes = mService.mRes;
     }
 
     /**
@@ -432,17 +425,13 @@
 
         mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
 
-        try {
-            if (DEBUG) {
-                Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
-                        + displayIdToShowIme);
-            }
-            mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD,
-                    displayIdToShowIme, null /* options */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not add window token " + mCurToken + " for display "
-                    + displayIdToShowIme, e);
+        if (DEBUG) {
+            Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+                    + displayIdToShowIme);
         }
+        mWindowManagerInternal.addWindowToken(mCurToken,
+                WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+                displayIdToShowIme, null /* options */);
     }
 
     @GuardedBy("ImfLock.class")
@@ -468,13 +457,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean bindCurrentInputMethodServiceVisibleConnection() {
-        mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
-                IME_VISIBLE_BIND_FLAGS);
-        return mVisibleBound;
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodServiceMainConnection() {
         mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
         return mHasConnection;
@@ -491,7 +473,8 @@
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
             if (mHasConnection && !mVisibleBound) {
-                bindCurrentInputMethodServiceVisibleConnection();
+                mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
+                        IME_VISIBLE_BIND_FLAGS);
             }
             return;
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
index 9d5393f..dc2799e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
@@ -25,11 +25,11 @@
  * Class for the device-level configuration related to the input method manager
  * platform features in {@link DeviceConfig}.
  */
-public final class InputMethodDeviceConfigs {
+final class InputMethodDeviceConfigs {
     private boolean mHideImeWhenNoEditorFocus;
     private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
 
-    public InputMethodDeviceConfigs() {
+    InputMethodDeviceConfigs() {
         mDeviceConfigChangedListener = properties -> {
             if (!DeviceConfig.NAMESPACE_INPUT_METHOD_MANAGER.equals(properties.getNamespace())) {
                 return;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed17b9ca..067c35f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -68,20 +68,18 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -92,7 +90,6 @@
 import android.graphics.Matrix;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManagerInternal;
 import android.net.Uri;
@@ -138,7 +135,6 @@
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -179,6 +175,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInputMethodManager;
@@ -188,6 +185,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
@@ -196,8 +194,6 @@
 import com.android.server.utils.PriorityDump;
 import com.android.server.wm.WindowManagerInternal;
 
-import com.google.android.collect.Sets;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -212,7 +208,6 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.OptionalInt;
-import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
@@ -284,7 +279,7 @@
      * {@link #mPreventImeStartupUnlessTextEditor}.
      */
     @NonNull
-    private final Set<String> mNonPreemptibleInputMethods;
+    private final String[] mNonPreemptibleInputMethods;
 
     @UserIdInt
     private int mLastSwitchUserId;
@@ -297,27 +292,21 @@
     final IWindowManager mIWindowManager;
     private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
             new SparseBooleanArray(0);
-    private final SparseBooleanArray mLoggedDeniedIsInputMethodPickerShownForTestForUid =
-            new SparseBooleanArray(0);
     final WindowManagerInternal mWindowManagerInternal;
+    private final ActivityManagerInternal mActivityManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
     final ImePlatformCompatUtils mImePlatformCompatUtils;
     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
     private final DisplayManagerInternal mDisplayManagerInternal;
-    final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
-    private final AppOpsManager mAppOpsManager;
     private final UserManager mUserManager;
     private final UserManagerInternal mUserManagerInternal;
     private final InputMethodMenuController mMenuController;
     @NonNull private final InputMethodBindingController mBindingController;
     @NonNull private final AutofillSuggestionsController mAutofillController;
 
-    // TODO(b/219056452): Use AccessibilityManagerInternal instead.
-    private final AccessibilityManager mAccessibilityManager;
-
     /**
      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
      *
@@ -807,7 +796,6 @@
     private LocaleList mLastSystemLocales;
     private boolean mAccessibilityRequestingNoSoftKeyboard;
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
-    private final IPackageManager mIPackageManager;
     private final String mSlotIme;
 
     /**
@@ -1478,7 +1466,6 @@
         public void onUidRemoved(int uid) {
             synchronized (ImfLock.class) {
                 mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
-                mLoggedDeniedIsInputMethodPickerShownForTestForUid.delete(uid);
             }
         }
 
@@ -1554,11 +1541,13 @@
                     int change = isPackageDisappearing(curIm.getPackageName());
                     if (change == PACKAGE_TEMPORARY_CHANGE
                             || change == PACKAGE_PERMANENT_CHANGE) {
+                        final PackageManager userAwarePackageManager =
+                                getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
                         ServiceInfo si = null;
                         try {
-                            si = mIPackageManager.getServiceInfo(
-                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
-                        } catch (RemoteException ex) {
+                            si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
+                                    PackageManager.ComponentInfoFlags.of(0));
+                        } catch (PackageManager.NameNotFoundException ignored) {
                         }
                         if (si == null) {
                             // Uh oh, current input method is no longer around!
@@ -1716,7 +1705,6 @@
     }
 
     public InputMethodManagerService(Context context) {
-        mIPackageManager = AppGlobals.getPackageManager();
         mContext = context;
         mRes = context.getResources();
         // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
@@ -1730,18 +1718,15 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mImePlatformCompatUtils = new ImePlatformCompatUtils();
         mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
         mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-        mAccessibilityManager = AccessibilityManager.getInstance(context);
-        mHasFeature = context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INPUT_METHODS);
 
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
 
@@ -1766,12 +1751,7 @@
         mShowOngoingImeSwitcherForPhones = false;
 
         mNotificationShown = false;
-        int userId = 0;
-        try {
-            userId = ActivityManager.getService().getCurrentUser().id;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
-        }
+        final int userId = mActivityManagerInternal.getCurrentUserId();
 
         mLastSwitchUserId = userId;
 
@@ -1787,8 +1767,8 @@
         mAutofillController = new AutofillSuggestionsController(this);
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
-        mNonPreemptibleInputMethods = Sets.newHashSet(mRes.getStringArray(
-                com.android.internal.R.array.config_nonPreemptibleInputMethods));
+        mNonPreemptibleInputMethods = mRes.getStringArray(
+                com.android.internal.R.array.config_nonPreemptibleInputMethods);
         mHwController = new HandwritingModeController(thread.getLooper(),
                 new InkWindowInitializer());
         registerDeviceListenerAndCheckStylusSupport();
@@ -2167,13 +2147,13 @@
      * Gets enabled subtypes of the specified {@link InputMethodInfo}.
      *
      * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
-     * @param allowsImplicitlySelectedSubtypes {@code true} to return the implicitly selected
+     * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled
      *                                         subtypes.
      * @param userId the user ID to be queried about.
      */
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
-            boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
@@ -2182,7 +2162,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 return getEnabledInputMethodSubtypeListLocked(imiId,
-                        allowsImplicitlySelectedSubtypes, userId);
+                        allowsImplicitlyEnabledSubtypes, userId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2191,7 +2171,7 @@
 
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
-            boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
         if (userId == mSettings.getCurrentUserId()) {
             final InputMethodInfo imi;
             String selectedMethodId = getSelectedMethodIdLocked();
@@ -2204,7 +2184,7 @@
                 return Collections.emptyList();
             }
             return mSettings.getEnabledInputMethodSubtypeListLocked(
-                    imi, allowsImplicitlySelectedSubtypes);
+                    imi, allowsImplicitlyEnabledSubtypes);
         }
         final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
         final InputMethodInfo imi = methodMap.get(imiId);
@@ -2214,7 +2194,7 @@
         final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
                 true);
         return settings.getEnabledInputMethodSubtypeListLocked(
-                imi, allowsImplicitlySelectedSubtypes);
+                imi, allowsImplicitlyEnabledSubtypes);
     }
 
     /**
@@ -2522,7 +2502,7 @@
                     null, null, null, selectedMethodId, getSequenceNumberLocked(), null, false);
         }
 
-        if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.mUid,
+        if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
                 editorInfo.packageName)) {
             Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
                     + " uid=" + cs.mUid + " package=" + editorInfo.packageName);
@@ -2615,23 +2595,20 @@
         if (!mPreventImeStartupUnlessTextEditor) {
             return false;
         }
-
-        final boolean imeVisibleAllowed =
-                isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags);
-
-        return !(imeVisibleAllowed
-                || mShowRequested
-                || isNonPreemptibleImeLocked(selectedMethodId));
-    }
-
-    /** Return {@code true} if the given IME is non-preemptible like the tv remote service. */
-    @GuardedBy("ImfLock.class")
-    private boolean isNonPreemptibleImeLocked(@NonNull  String selectedMethodId) {
-        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
-        if (imi != null) {
-            return mNonPreemptibleInputMethods.contains(imi.getPackageName());
+        if (mShowRequested) {
+            return false;
         }
-        return false;
+        if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
+            return false;
+        }
+        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        if (imi == null) {
+            return false;
+        }
+        if (ArrayUtils.contains(mNonPreemptibleInputMethods, imi.getPackageName())) {
+            return false;
+        }
+        return true;
     }
 
     @GuardedBy("ImfLock.class")
@@ -2959,19 +2936,17 @@
                     hideStatusBarIconLocked();
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
-                    CharSequence contentDescription = null;
+                    final PackageManager userAwarePackageManager =
+                            getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
+                    ApplicationInfo applicationInfo = null;
                     try {
-                        // Use PackageManager to load label
-                        final PackageManager packageManager = mContext.getPackageManager();
-                        final ApplicationInfo applicationInfo = mIPackageManager
-                                .getApplicationInfo(packageName, 0, mSettings.getCurrentUserId());
-                        if (applicationInfo != null) {
-                            contentDescription = packageManager
-                                    .getApplicationLabel(applicationInfo);
-                        }
-                    } catch (RemoteException e) {
-                        /* ignore */
+                        applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
+                                PackageManager.ApplicationInfoFlags.of(0));
+                    } catch (PackageManager.NameNotFoundException e) {
                     }
+                    final CharSequence contentDescription = applicationInfo != null
+                            ? userAwarePackageManager.getApplicationLabel(applicationInfo)
+                            : null;
                     if (mStatusBar != null) {
                         mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
                                 contentDescription  != null
@@ -3223,27 +3198,30 @@
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
         if (enabledMayChange) {
+            final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
+                    mSettings.getCurrentUserId());
+
             List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
                 InputMethodInfo imm = enabled.get(i);
+                ApplicationInfo ai = null;
                 try {
-                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
-                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
-                            mSettings.getCurrentUserId());
-                    if (ai != null && ai.enabledSetting
-                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Update state(" + imm.getId()
-                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
-                        }
-                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
-                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
-                                mContext.getBasePackageName());
+                    ai = userAwarePackageManager.getApplicationInfo(imm.getPackageName(),
+                            PackageManager.ApplicationInfoFlags.of(
+                                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS));
+                } catch (PackageManager.NameNotFoundException ignored) {
+                }
+                if (ai != null && ai.enabledSetting
+                        == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Update state(" + imm.getId()
+                                + "): DISABLED_UNTIL_USED -> DEFAULT");
                     }
-                } catch (RemoteException e) {
+                    userAwarePackageManager.setApplicationEnabledSetting(imm.getPackageName(),
+                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                            PackageManager.DONT_KILL_APP);
                 }
             }
         }
@@ -3325,7 +3303,7 @@
             // setSelectedInputMethodAndSubtypeLocked().
             setSelectedMethodIdLocked(id);
 
-            if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
+            if (mActivityManagerInternal.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
                 intent.putExtra("input_method_id", id);
@@ -3961,7 +3939,7 @@
             return false;
         }
         if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
-                mAppOpsManager,
+                mPackageManagerInternal,
                 uid,
                 getCurIntentLocked().getComponent().getPackageName())) {
             return true;
@@ -4001,19 +3979,8 @@
     /**
      * A test API for CTS to make sure that the input method menu is showing.
      */
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
-        if (mContext.checkCallingPermission(android.Manifest.permission.TEST_INPUT_METHOD)
-                != PackageManager.PERMISSION_GRANTED) {
-            final int callingUid = Binder.getCallingUid();
-            synchronized (ImfLock.class) {
-                if (!mLoggedDeniedIsInputMethodPickerShownForTestForUid.get(callingUid)) {
-                    EventLog.writeEvent(0x534e4554, "237317525", callingUid, "");
-                    mLoggedDeniedIsInputMethodPickerShownForTestForUid.put(callingUid, true);
-                }
-            }
-            throw new SecurityException(
-                    "isInputMethodPickerShownForTest requires TEST_INPUT_METHOD permission");
-        }
         synchronized (ImfLock.class) {
             return mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4171,6 +4138,7 @@
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
+        final int callingUid = Binder.getCallingUid();
 
         // By this IPC call, only a process which shares the same uid with the IME can add
         // additional input method subtypes to the IME.
@@ -4191,7 +4159,7 @@
 
             if (mSettings.getCurrentUserId() == userId) {
                 if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
-                        mAdditionalSubtypeMap, mIPackageManager)) {
+                        mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) {
                     return;
                 }
                 final long ident = Binder.clearCallingIdentity();
@@ -4203,14 +4171,57 @@
                 return;
             }
 
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
-                    userId, false);
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
             final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+                    methodList, DirectBootAwareness.AUTO);
+            final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
+                    userId, false);
             settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
-                    mIPackageManager);
+                    mPackageManagerInternal, callingUid);
+        }
+    }
+
+    @Override
+    public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        }
+        final int callingUid = Binder.getCallingUid();
+        final ComponentName imeComponentName =
+                imeId != null ? ComponentName.unflattenFromString(imeId) : null;
+        if (imeComponentName == null || !InputMethodUtils.checkIfPackageBelongsToUid(
+                mPackageManagerInternal, callingUid, imeComponentName.getPackageName())) {
+            throw new SecurityException("Calling UID=" + callingUid + " does not belong to imeId="
+                    + imeId);
+        }
+        Objects.requireNonNull(subtypeHashCodes, "subtypeHashCodes must not be null");
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (ImfLock.class) {
+                final boolean currentUser = (mSettings.getCurrentUserId() == userId);
+                final InputMethodSettings settings = currentUser
+                        ? mSettings
+                        : new InputMethodSettings(mContext, queryMethodMapForUser(userId), userId,
+                                !mUserManagerInternal.isUserUnlocked(userId));
+                if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
+                    return;
+                }
+                if (currentUser) {
+                    // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
+                    if (mSettingsObserver != null) {
+                        mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr();
+                    }
+                    updateInputMethodsFromSettingsLocked(false /* enabledChanged */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -4993,6 +5004,10 @@
             @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
             ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
             @DirectBootAwareness int directBootAwareness) {
+        final Context userAwareContext = context.getUserId() == userId
+                ? context
+                : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+
         methodList.clear();
         methodMap.clear();
 
@@ -5014,8 +5029,9 @@
         final int flags = PackageManager.GET_META_DATA
                 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                 | directBootAwarenessFlags;
-        final List<ResolveInfo> services = context.getPackageManager().queryIntentServicesAsUser(
-                new Intent(InputMethod.SERVICE_INTERFACE), flags, userId);
+        final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices(
+                new Intent(InputMethod.SERVICE_INTERFACE),
+                PackageManager.ResolveInfoFlags.of(flags));
 
         methodList.ensureCapacity(services.size());
         methodMap.ensureCapacity(services.size());
@@ -5034,7 +5050,7 @@
             if (DEBUG) Slog.d(TAG, "Checking " + imeId);
 
             try {
-                final InputMethodInfo imi = new InputMethodInfo(context, ri,
+                final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri,
                         additionalSubtypeMap.get(imeId));
                 if (imi.isVrOnly()) {
                     continue;  // Skip VR-only IME, which isn't supported for now.
@@ -5438,7 +5454,8 @@
         public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
                 InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
             // Get the device global touch exploration state before lock to avoid deadlock.
-            boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
+            final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
+                    .isTouchExplorationEnabled(userId);
 
             synchronized (ImfLock.class) {
                 mAutofillController.onCreateInlineSuggestionsRequest(userId, requestInfo, cb,
@@ -6379,9 +6396,9 @@
      * @return {@code true} if userId has debugging privileges.
      * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}.
      */
-    private boolean userHasDebugPriv(int userId, final ShellCommand shellCommand) {
-        if (mUserManager.hasUserRestriction(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) {
+    private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
+        if (mUserManagerInternal.hasUserRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, userId)) {
             shellCommand.getErrPrintWriter().println("User #" + userId
                     + " is restricted with DISALLOW_DEBUGGING_FEATURES.");
             return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 11e6923..a25630f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -71,7 +71,7 @@
     @Nullable
     private InputMethodDialogWindowContext mDialogWindowContext;
 
-    public InputMethodMenuController(InputMethodManagerService service) {
+    InputMethodMenuController(InputMethodManagerService service) {
         mService = service;
         mSettings = mService.mSettings;
         mSwitchingController = mService.mSwitchingController;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 1747b5c..69b0661 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -20,21 +20,19 @@
 import android.annotation.Nullable;
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
-import android.os.Binder;
 import android.os.Build;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
 import android.util.Slog;
@@ -42,8 +40,8 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.textservice.SpellCheckerInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.StartInputFlags;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.textservices.TextServicesManagerInternal;
@@ -78,29 +76,6 @@
     }
 
     // ----------------------------------------------------------------------
-    // Utilities for debug
-    static String getApiCallStack() {
-        String apiCallStack = "";
-        try {
-            throw new RuntimeException();
-        } catch (RuntimeException e) {
-            final StackTraceElement[] frames = e.getStackTrace();
-            for (int j = 1; j < frames.length; ++j) {
-                final String tempCallStack = frames[j].toString();
-                if (TextUtils.isEmpty(apiCallStack)) {
-                    // Overwrite apiCallStack if it's empty
-                    apiCallStack = tempCallStack;
-                } else if (tempCallStack.indexOf("Transact(") < 0) {
-                    // Overwrite apiCallStack if it's not a binder call
-                    apiCallStack = tempCallStack;
-                } else {
-                    break;
-                }
-            }
-        }
-        return apiCallStack;
-    }
-    // ----------------------------------------------------------------------
 
     static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
         if (subtype == null) return true;
@@ -208,28 +183,27 @@
         return subtype != null
                 ? TextUtils.concat(subtype.getDisplayName(context,
                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
-                                (TextUtils.isEmpty(imiLabel) ?
-                                        "" : " - " + imiLabel))
+                                (TextUtils.isEmpty(imiLabel) ? "" : " - " + imiLabel))
                 : imiLabel;
     }
 
     /**
      * Returns true if a package name belongs to a UID.
      *
-     * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
-     * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
+     * <p>This is a simple wrapper of
+     * {@link PackageManagerInternal#getPackageUid(String, long, int)}.</p>
+     * @param packageManagerInternal the {@link PackageManagerInternal} object to be used for the
+     *                               validation.
      * @param uid the UID to be validated.
      * @param packageName the package name.
      * @return {@code true} if the package name belongs to the UID.
      */
-    static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
-            @UserIdInt int uid, String packageName) {
-        try {
-            appOpsManager.checkPackage(uid, packageName);
-            return true;
-        } catch (SecurityException e) {
-            return false;
-        }
+    static boolean checkIfPackageBelongsToUid(PackageManagerInternal packageManagerInternal,
+            int uid, String packageName) {
+        // PackageManagerInternal#getPackageUid() doesn't check MATCH_INSTANT/MATCH_APEX as of
+        // writing. So setting 0 should be fine.
+        return packageManagerInternal.getPackageUid(packageName, 0 /* flags */,
+                UserHandle.getUserId(uid)) == uid;
     }
 
     /**
@@ -415,10 +389,10 @@
         }
 
         List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
-                InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
+                InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
             List<InputMethodSubtype> enabledSubtypes =
                     getEnabledInputMethodSubtypeListLocked(imi);
-            if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
+            if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
                 enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
             }
             return InputMethodSubtype.sort(imi, enabledSubtypes);
@@ -669,12 +643,12 @@
                         // If IME is enabled and no subtypes are enabled, applicable subtypes
                         // are enabled implicitly, so needs to treat them to be enabled.
                         if (imi != null && imi.getSubtypeCount() > 0) {
-                            List<InputMethodSubtype> implicitlySelectedSubtypes =
+                            List<InputMethodSubtype> implicitlyEnabledSubtypes =
                                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
-                            if (implicitlySelectedSubtypes != null) {
-                                final int N = implicitlySelectedSubtypes.size();
-                                for (int i = 0; i < N; ++i) {
-                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
+                            if (implicitlyEnabledSubtypes != null) {
+                                final int numSubtypes = implicitlyEnabledSubtypes.size();
+                                for (int i = 0; i < numSubtypes; ++i) {
+                                    final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
                                         return subtypeHashCode;
                                     }
@@ -875,20 +849,13 @@
         boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
                 @NonNull ArrayList<InputMethodSubtype> subtypes,
                 @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
-                @NonNull IPackageManager packageManager) {
+                @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
             final InputMethodInfo imi = mMethodMap.get(imeId);
             if (imi == null) {
                 return false;
             }
-            final String[] packageInfos;
-            try {
-                packageInfos = packageManager.getPackagesForUid(Binder.getCallingUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to get package infos");
-                return false;
-            }
-            if (ArrayUtils.find(packageInfos,
-                    packageInfo -> TextUtils.equals(packageInfo, imi.getPackageName())) == null) {
+            if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
+                    imi.getPackageName())) {
                 return false;
             }
 
@@ -901,6 +868,72 @@
             return true;
         }
 
+        boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
+                @NonNull int[] subtypeHashCodes) {
+            final InputMethodInfo imi = mMethodMap.get(imeId);
+            if (imi == null) {
+                return false;
+            }
+
+            final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
+            for (int subtypeHashCode : subtypeHashCodes) {
+                if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
+                    continue;  // NOT_A_SUBTYPE_ID must not be saved
+                }
+                if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
+                    continue;  // this subtype does not exist in InputMethodInfo.
+                }
+                if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
+                    continue;  // The entry is already added.  No need to add anymore.
+                }
+                validSubtypeHashCodes.add(subtypeHashCode);
+            }
+
+            final String originalEnabledImesString = getEnabledInputMethodsStr();
+            final String updatedEnabledImesString = updateEnabledImeString(
+                    originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
+            if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
+                return false;
+            }
+
+            putEnabledInputMethodsStr(updatedEnabledImesString);
+            return true;
+        }
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        static String updateEnabledImeString(@NonNull String enabledImesString,
+                @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
+            final TextUtils.SimpleStringSplitter imeSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+            final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+
+            final StringBuilder sb = new StringBuilder();
+
+            imeSplitter.setString(enabledImesString);
+            boolean needsImeSeparator = false;
+            while (imeSplitter.hasNext()) {
+                final String nextImsStr = imeSplitter.next();
+                imeSubtypeSplitter.setString(nextImsStr);
+                if (imeSubtypeSplitter.hasNext()) {
+                    if (needsImeSeparator) {
+                        sb.append(INPUT_METHOD_SEPARATOR);
+                    }
+                    if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
+                        sb.append(imeId);
+                        for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
+                            sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
+                            sb.append(enabledSubtypeHashCodes.get(i));
+                        }
+                    } else {
+                        sb.append(nextImsStr);
+                    }
+                    needsImeSeparator = true;
+                }
+            }
+            return sb.toString();
+        }
+
         public void dumpLocked(final Printer pw, final String prefix) {
             pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
             pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index 3d02b3a..f865e60 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -46,7 +46,7 @@
      * @param desired The locale preferred by user.
      * @return A score based on the locale matching for the default subtype enabling.
      */
-    @IntRange(from=1, to=3)
+    @IntRange(from = 1, to = 3)
     private static byte calculateMatchingSubScore(@NonNull final ULocale supported,
             @NonNull final ULocale desired) {
         // Assuming supported/desired is fully expanded.
@@ -111,7 +111,7 @@
          * @return 1 if {@code left} is larger than {@code right}. -1 if {@code left} is less than
          * {@code right}. 0 if {@code left} and {@code right} is equal.
          */
-        @IntRange(from=-1, to=1)
+        @IntRange(from = -1, to = 1)
         private static int compare(@NonNull byte[] left, @NonNull byte[] right) {
             for (int i = 0; i < left.length; ++i) {
                 if (left[i] > right[i]) {
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 7085868..f07539f 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -68,14 +68,14 @@
         if (locale == null) {
             return false;
         }
-        final int N = imi.getSubtypeCount();
-        for (int i = 0; i < N; ++i) {
+        final int numSubtypes = imi.getSubtypeCount();
+        for (int i = 0; i < numSubtypes; ++i) {
             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
             if (checkCountry) {
                 final Locale subtypeLocale = subtype.getLocaleObject();
-                if (subtypeLocale == null ||
-                        !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
-                        !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
+                if (subtypeLocale == null
+                        || !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())
+                        || !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
                     continue;
                 }
             } else {
@@ -260,8 +260,8 @@
         boolean partialMatchFound = false;
         InputMethodSubtype applicableSubtype = null;
         InputMethodSubtype firstMatchedModeSubtype = null;
-        final int N = subtypes.size();
-        for (int i = 0; i < N; ++i) {
+        final int numSubtypes = subtypes.size();
+        for (int i = 0; i < numSubtypes; ++i) {
             InputMethodSubtype subtype = subtypes.get(i);
             final String subtypeLocale = subtype.getLocale();
             final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString(subtypeLocale);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 1d1057f..9bd48f2 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -115,6 +115,7 @@
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper;
 import com.android.server.location.injector.SettingsHelper;
 import com.android.server.location.injector.SystemAlarmHelper;
@@ -125,6 +126,7 @@
 import com.android.server.location.injector.SystemEmergencyHelper;
 import com.android.server.location.injector.SystemLocationPermissionsHelper;
 import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
+import com.android.server.location.injector.SystemPackageResetHelper;
 import com.android.server.location.injector.SystemScreenInteractiveHelper;
 import com.android.server.location.injector.SystemSettingsHelper;
 import com.android.server.location.injector.SystemUserInfoHelper;
@@ -1696,11 +1698,13 @@
         private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
         private final SystemDeviceIdleHelper mDeviceIdleHelper;
         private final LocationUsageLogger mLocationUsageLogger;
+        private final PackageResetHelper mPackageResetHelper;
 
         // lazily instantiated since they may not always be used
 
         @GuardedBy("this")
-        private @Nullable SystemEmergencyHelper mEmergencyCallHelper;
+        @Nullable
+        private SystemEmergencyHelper mEmergencyCallHelper;
 
         @GuardedBy("this")
         private boolean mSystemReady;
@@ -1721,6 +1725,7 @@
             mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
             mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
             mLocationUsageLogger = new LocationUsageLogger();
+            mPackageResetHelper = new SystemPackageResetHelper(context);
         }
 
         synchronized void onSystemReady() {
@@ -1811,5 +1816,10 @@
         public LocationUsageLogger getLocationUsageLogger() {
             return mLocationUsageLogger;
         }
+
+        @Override
+        public PackageResetHelper getPackageResetHelper() {
+            return mPackageResetHelper;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 82bcca2..349b94b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -39,6 +39,7 @@
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationPermissionsHelper;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.SettingsHelper;
 import com.android.server.location.injector.UserInfoHelper;
 import com.android.server.location.injector.UserInfoHelper.UserListener;
@@ -193,6 +194,7 @@
     protected final LocationPermissionsHelper mLocationPermissionsHelper;
     protected final AppForegroundHelper mAppForegroundHelper;
     protected final LocationManagerInternal mLocationManagerInternal;
+    private final PackageResetHelper mPackageResetHelper;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final ProviderEnabledListener mProviderEnabledChangedListener =
@@ -218,12 +220,25 @@
             };
     private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener =
             this::onAppForegroundChanged;
+    private final PackageResetHelper.Responder mPackageResetResponder =
+            new PackageResetHelper.Responder() {
+                @Override
+                public void onPackageReset(String packageName) {
+                    GnssListenerMultiplexer.this.onPackageReset(packageName);
+                }
+
+                @Override
+                public boolean isResetableForPackage(String packageName) {
+                    return GnssListenerMultiplexer.this.isResetableForPackage(packageName);
+                }
+            };
 
     protected GnssListenerMultiplexer(Injector injector) {
         mUserInfoHelper = injector.getUserInfoHelper();
         mSettingsHelper = injector.getSettingsHelper();
         mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
         mAppForegroundHelper = injector.getAppForegroundHelper();
+        mPackageResetHelper = injector.getPackageResetHelper();
         mLocationManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(LocationManagerInternal.class));
     }
@@ -357,6 +372,7 @@
                 mLocationPackageBlacklistChangedListener);
         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
         mAppForegroundHelper.addListener(mAppForegroundChangedListener);
+        mPackageResetHelper.register(mPackageResetResponder);
     }
 
     @Override
@@ -374,6 +390,7 @@
                 mLocationPackageBlacklistChangedListener);
         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
         mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
+        mPackageResetHelper.unregister(mPackageResetResponder);
     }
 
     private void onUserChanged(int userId, int change) {
@@ -407,6 +424,24 @@
         updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
     }
 
+    private void onPackageReset(String packageName) {
+        updateRegistrations(
+                registration -> {
+                    if (registration.getIdentity().getPackageName().equals(
+                            packageName)) {
+                        registration.remove();
+                    }
+
+                    return false;
+                });
+    }
+
+    private boolean isResetableForPackage(String packageName) {
+        // invoked to find out if the given package has any state that can be "force quit"
+        return findRegistration(
+                registration -> registration.getIdentity().getPackageName().equals(packageName));
+    }
+
     @Override
     protected String getServiceState() {
         if (!isSupported()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index a6a3db1..e653f04 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1616,6 +1616,10 @@
             updateEnabled();
             restartLocationRequest();
         }
+
+        // Re-register network callbacks to get an update of available networks right away.
+        mNetworkConnectivityHandler.unregisterNetworkCallbacks();
+        mNetworkConnectivityHandler.registerNetworkCallbacks();
     }
 
     @Override
@@ -1722,7 +1726,8 @@
         String setId = null;
 
         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
-        if (mNIHandler.getInEmergency() && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
+        if (mGnssConfiguration.isActiveSimEmergencySuplEnabled() && mNIHandler.getInEmergency()
+                && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
             subId = mNetworkConnectivityHandler.getActiveSubId();
         }
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
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 9f2a9cf..07e9fe6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -112,6 +112,9 @@
     @Override
     protected boolean registerWithService(GnssMeasurementRequest request,
             Collection<GnssListenerRegistration> registrations) {
+        if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+            return true;
+        }
         if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
                 request.isCorrelationVectorOutputsEnabled(),
                 request.getIntervalMillis())) {
@@ -157,7 +160,7 @@
             Collection<GnssListenerRegistration> registrations) {
         boolean fullTracking = false;
         boolean enableCorrVecOutputs = false;
-        int intervalMillis = Integer.MAX_VALUE;
+        int intervalMillis = GnssMeasurementRequest.PASSIVE_INTERVAL;
 
         if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
             fullTracking = true;
@@ -165,6 +168,10 @@
 
         for (GnssListenerRegistration registration : registrations) {
             GnssMeasurementRequest request = registration.getRequest();
+            // passive requests do not contribute to the merged request
+            if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+                continue;
+            }
             if (request.isFullTracking()) {
                 fullTracking = true;
             }
@@ -175,10 +182,10 @@
         }
 
         return new GnssMeasurementRequest.Builder()
-                    .setFullTracking(fullTracking)
-                    .setCorrelationVectorOutputsEnabled(enableCorrVecOutputs)
-                    .setIntervalMillis(intervalMillis)
-                    .build();
+                .setFullTracking(fullTracking)
+                .setCorrelationVectorOutputsEnabled(enableCorrVecOutputs)
+                .setIntervalMillis(intervalMillis)
+                .build();
     }
 
     @Override
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 dc1f4dd..02bdfd5 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -301,6 +301,10 @@
         mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
     }
 
+    void unregisterNetworkCallbacks() {
+        mConnMgr.unregisterNetworkCallback(mNetworkConnectivityCallback);
+    }
+
     /**
      * @return {@code true} if there is a data network available for outgoing connections,
      * {@code false} otherwise.
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 c0ce3a6..b2c8672 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -63,4 +63,7 @@
 
     /** Returns a LocationUsageLogger. */
     LocationUsageLogger getLocationUsageLogger();
+
+    /** Returns a PackageResetHelper. */
+    PackageResetHelper getPackageResetHelper();
 }
diff --git a/services/core/java/com/android/server/location/injector/PackageResetHelper.java b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
new file mode 100644
index 0000000..721c576
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Helpers for tracking queries and resets of package state. */
+public abstract class PackageResetHelper {
+
+    /** Interface for responding to reset events. */
+    public interface Responder {
+
+        /**
+         * Called when a package's runtime state is being reset for whatever reason and any
+         * components carrying runtime state on behalf of the package should clear that state.
+         *
+         * @param packageName The name of the package.
+         */
+        void onPackageReset(String packageName);
+
+        /**
+         * Called when the system queries whether this package has any active state for the given
+         * package. Should return true if the component has some runtime state that is resetable of
+         * behalf of the given package, and false otherwise.
+         *
+         * @param packageName The name of the package.
+         * @return True if this component has resetable state for the given package.
+         */
+        boolean isResetableForPackage(String packageName);
+    }
+
+    private final CopyOnWriteArrayList<Responder> mResponders;
+
+    public PackageResetHelper() {
+        mResponders = new CopyOnWriteArrayList<>();
+    }
+
+    /** Begin listening for package reset events. */
+    public synchronized void register(Responder responder) {
+        boolean empty = mResponders.isEmpty();
+        mResponders.add(responder);
+        if (empty) {
+            onRegister();
+        }
+    }
+
+    /** Stop listening for package reset events. */
+    public synchronized void unregister(Responder responder) {
+        mResponders.remove(responder);
+        if (mResponders.isEmpty()) {
+            onUnregister();
+        }
+    }
+
+    @GuardedBy("this")
+    protected abstract void onRegister();
+
+    @GuardedBy("this")
+    protected abstract void onUnregister();
+
+    protected final void notifyPackageReset(String packageName) {
+        if (D) {
+            Log.d(TAG, "package " + packageName + " reset");
+        }
+
+        for (Responder responder : mResponders) {
+            responder.onPackageReset(packageName);
+        }
+    }
+
+    protected final boolean queryResetableForPackage(String packageName) {
+        for (Responder responder : mResponders) {
+            if (responder.isResetableForPackage(packageName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
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 dc52990..1fb00ef 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location.injector;
 
+import static com.android.server.location.LocationManagerService.TAG;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -23,6 +25,7 @@
 import android.os.SystemClock;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 
 import com.android.server.FgThread;
 
@@ -67,8 +70,12 @@
                 }
 
                 synchronized (SystemEmergencyHelper.this) {
-                    mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
-                            intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+                    try {
+                        mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
+                                intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+                    } catch (IllegalStateException e) {
+                        Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
+                    }
                 }
             }
         }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL));
diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
new file mode 100644
index 0000000..b0fe55d
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+/** Listens to appropriate broadcasts for queries and resets. */
+public class SystemPackageResetHelper extends PackageResetHelper {
+
+    private final Context mContext;
+
+    @Nullable
+    private BroadcastReceiver mReceiver;
+
+    public SystemPackageResetHelper(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    protected void onRegister() {
+        Preconditions.checkState(mReceiver == null);
+        mReceiver = new Receiver();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+        filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        filter.addDataScheme("package");
+
+        // We don't filter for Intent.ACTION_PACKAGE_DATA_CLEARED as 1) it refers to persistent
+        // data, and 2) it should always be preceded by Intent.ACTION_PACKAGE_RESTARTED, which
+        // refers to runtime data. in this way we also avoid redundant callbacks.
+
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    protected void onUnregister() {
+        Preconditions.checkState(mReceiver != null);
+        mContext.unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
+
+    private class Receiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
+            Uri data = intent.getData();
+            if (data == null) {
+                return;
+            }
+
+            String packageName = data.getSchemeSpecificPart();
+            if (packageName == null) {
+                return;
+            }
+
+            switch (action) {
+                case Intent.ACTION_QUERY_PACKAGE_RESTART:
+                    String[] packages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                    if (packages != null) {
+                        // it would be more efficient to pass through the whole array, but at the
+                        // moment the array is always size 1, and this makes for a nicer callback.
+                        for (String pkg : packages) {
+                            if (queryResetableForPackage(pkg)) {
+                                setResultCode(Activity.RESULT_OK);
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_CHANGED:
+                    // make sure this is an enabled/disabled change to the package as a whole, not
+                    // just some of its components. This avoids unnecessary work in the callback.
+                    boolean isPackageChange = false;
+                    String[] components = intent.getStringArrayExtra(
+                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                    if (components != null) {
+                        for (String component : components) {
+                            if (packageName.equals(component)) {
+                                isPackageChange = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (isPackageChange) {
+                        try {
+                            ApplicationInfo appInfo =
+                                    context.getPackageManager().getApplicationInfo(packageName,
+                                            PackageManager.ApplicationInfoFlags.of(0));
+                            if (!appInfo.enabled) {
+                                // move off main thread
+                                FgThread.getExecutor().execute(
+                                        () -> notifyPackageReset(packageName));
+                            }
+                        } catch (PackageManager.NameNotFoundException e) {
+                            return;
+                        }
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    // fall through
+                case Intent.ACTION_PACKAGE_RESTARTED:
+                    // move off main thread
+                    FgThread.getExecutor().execute(() -> notifyPackageReset(packageName));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
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 a69a079..338a995 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -106,6 +106,7 @@
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
 import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
 import com.android.server.location.injector.SettingsHelper;
@@ -1373,6 +1374,7 @@
     protected final ScreenInteractiveHelper mScreenInteractiveHelper;
     protected final LocationUsageLogger mLocationUsageLogger;
     protected final LocationFudger mLocationFudger;
+    private final PackageResetHelper mPackageResetHelper;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener =
@@ -1407,6 +1409,18 @@
             this::onLocationPowerSaveModeChanged;
     private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
             this::onScreenInteractiveChanged;
+    private final PackageResetHelper.Responder mPackageResetResponder =
+            new PackageResetHelper.Responder() {
+                @Override
+                public void onPackageReset(String packageName) {
+                    LocationProviderManager.this.onPackageReset(packageName);
+                }
+
+                @Override
+                public boolean isResetableForPackage(String packageName) {
+                    return LocationProviderManager.this.isResetableForPackage(packageName);
+                }
+            };
 
     // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
     protected final MockableLocationProvider mProvider;
@@ -1442,6 +1456,7 @@
         mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
         mLocationUsageLogger = injector.getLocationUsageLogger();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+        mPackageResetHelper = injector.getPackageResetHelper();
 
         mProvider = new MockableLocationProvider(mMultiplexerLock);
 
@@ -1970,6 +1985,7 @@
         mAppForegroundHelper.addListener(mAppForegroundChangedListener);
         mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
         mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+        mPackageResetHelper.register(mPackageResetResponder);
     }
 
     @GuardedBy("mMultiplexerLock")
@@ -1988,6 +2004,7 @@
         mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
         mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
         mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+        mPackageResetHelper.unregister(mPackageResetResponder);
     }
 
     @GuardedBy("mMultiplexerLock")
@@ -2391,6 +2408,24 @@
         updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
     }
 
+    private void onPackageReset(String packageName) {
+        updateRegistrations(
+                registration -> {
+                    if (registration.getIdentity().getPackageName().equals(
+                            packageName)) {
+                        registration.remove();
+                    }
+
+                    return false;
+                });
+    }
+
+    private boolean isResetableForPackage(String packageName) {
+        // invoked to find out if the given package has any state that can be "force quit"
+        return findRegistration(
+                registration -> registration.getIdentity().getPackageName().equals(packageName));
+    }
+
     @GuardedBy("mMultiplexerLock")
     @Override
     public void onStateChanged(
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8ab3a94..58d677c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -113,6 +113,7 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -141,6 +142,7 @@
 import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
 import libcore.util.HexEncoding;
@@ -243,6 +245,17 @@
 
     private final RebootEscrowManager mRebootEscrowManager;
 
+    // Locking order is mUserCreationAndRemovalLock -> mSpManager.
+    private final Object mUserCreationAndRemovalLock = new Object();
+    // These two arrays are only used at boot time.  To save memory, they are set to null when
+    // PHASE_BOOT_COMPLETED is reached.
+    @GuardedBy("mUserCreationAndRemovalLock")
+    private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
+    @GuardedBy("mUserCreationAndRemovalLock")
+    private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
+    @GuardedBy("mUserCreationAndRemovalLock")
+    private boolean mBootComplete;
+
     // Current password metric for all users on the device. Updated when user unlocks
     // the device or changes password. Removed when user is stopped.
     @GuardedBy("this")
@@ -283,9 +296,16 @@
         @Override
         public void onBootPhase(int phase) {
             super.onBootPhase(phase);
-            if (phase == PHASE_ACTIVITY_MANAGER_READY) {
-                mLockSettingsService.migrateOldDataAfterSystemReady();
-                mLockSettingsService.loadEscrowData();
+            switch (phase) {
+                case PHASE_ACTIVITY_MANAGER_READY:
+                    mLockSettingsService.migrateOldDataAfterSystemReady();
+                    mLockSettingsService.loadEscrowData();
+                    break;
+                case PHASE_BOOT_COMPLETED:
+                    mLockSettingsService.bootCompleted();
+                    break;
+                default:
+                    break;
             }
         }
 
@@ -577,7 +597,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_STARTING);
-        filter.addAction(Intent.ACTION_USER_REMOVED);
         injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
                 null, null);
 
@@ -720,28 +739,32 @@
     }
 
     /**
-     * Clean up states associated with the given user, in case the userId is reused but LSS didn't
-     * get a chance to do cleanup previously during ACTION_USER_REMOVED.
-     *
-     * Internally, LSS stores serial number for each user and check it against the current user's
-     * serial number to determine if the userId is reused and invoke cleanup code.
+     * Removes the LSS state for the given userId if the userId was reused without its LSS state
+     * being fully removed.
+     * <p>
+     * This is primarily needed for users that were removed by Android 13 or earlier, which didn't
+     * guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent.  It is
+     * also needed because {@link #removeUser()} delays requests to remove LSS state until the
+     * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost.
+     * <p>
+     * Stale state is detected by checking whether the user serial number changed.  This works
+     * because user serial numbers are never reused.
      */
-    private void cleanupDataForReusedUserIdIfNecessary(int userId) {
+    private void removeStateForReusedUserIdIfNecessary(@UserIdInt int userId, int serialNumber) {
         if (userId == UserHandle.USER_SYSTEM) {
             // Short circuit as we never clean up user 0.
             return;
         }
-        // Serial number is never reusued, so we can use it as a distinguisher for user Id reuse.
-        int serialNumber = mUserManager.getUserSerialNumber(userId);
-
         int storedSerialNumber = mStorage.getInt(USER_SERIAL_NUMBER_KEY, -1, userId);
         if (storedSerialNumber != serialNumber) {
             // If LockSettingsStorage does not have a copy of the serial number, it could be either
             // this is a user created before the serial number recording logic is introduced, or
             // the user does not exist or was removed and cleaned up properly. In either case, don't
-            // invoke removeUser().
+            // invoke removeUserState().
             if (storedSerialNumber != -1) {
-                removeUser(userId, /* unknownUser */ true);
+                Slogf.i(TAG, "Removing stale state for reused userId %d (serial %d => %d)", userId,
+                        storedSerialNumber, serialNumber);
+                removeUserState(userId);
             }
             mStorage.setInt(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
         }
@@ -771,7 +794,6 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                cleanupDataForReusedUserIdIfNecessary(userId);
                 ensureProfileKeystoreUnlocked(userId);
                 // Hide notification first, as tie managed profile lock takes time
                 hideEncryptionNotification(new UserHandle(userId));
@@ -779,38 +801,10 @@
                 if (isCredentialSharableWithParent(userId)) {
                     tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
                 }
-
-                // If the user doesn't have a credential, try and derive their secret for the
-                // AuthSecret HAL. The secret will have been enrolled if the user previously set a
-                // credential and still needs to be passed to the HAL once that credential is
-                // removed.
-                if (mUserManager.getUserInfo(userId).isPrimary() && !isUserSecure(userId)) {
-                    tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(userId);
-                }
             }
         });
     }
 
-    private void tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(@UserIdInt int userId) {
-        synchronized (mSpManager) {
-            // If there is no SP, then there is no vendor auth secret.
-            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
-                return;
-            }
-
-            final long protectorId = getCurrentLskfBasedProtectorId(userId);
-            AuthenticationResult result =
-                    mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
-                            LockscreenCredential.createNone(), userId, null);
-            if (result.syntheticPassword != null) {
-                Slog.i(TAG, "Unwrapped SP for unsecured primary user " + userId);
-                onSyntheticPasswordKnown(userId, result.syntheticPassword);
-            } else {
-                Slog.e(TAG, "Failed to unwrap SP for unsecured primary user " + userId);
-            }
-        }
-    }
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -821,11 +815,6 @@
             } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
                 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 mStorage.prefetchUser(userHandle);
-            } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
-                final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-                if (userHandle > 0) {
-                    removeUser(userHandle, /* unknownUser= */ false);
-                }
             }
         }
     };
@@ -937,6 +926,79 @@
         return success;
     }
 
+    private void bootCompleted() {
+        synchronized (mUserCreationAndRemovalLock) {
+            // Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
+            for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
+                int userId = mEarlyRemovedUsers.keyAt(i);
+                Slogf.i(TAG, "Removing locksettings state for removed user %d now that boot "
+                        + "is complete", userId);
+                removeUserState(userId);
+            }
+            mEarlyRemovedUsers = null; // no longer needed
+            for (int i = 0; i < mEarlyCreatedUsers.size(); i++) {
+                int userId = mEarlyCreatedUsers.keyAt(i);
+                int serialNumber = mEarlyCreatedUsers.valueAt(i);
+
+                removeStateForReusedUserIdIfNecessary(userId, serialNumber);
+                synchronized (mSpManager) {
+                    if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+                        Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
+                                + "is complete", userId);
+                        initializeSyntheticPasswordLocked(userId);
+                    }
+                }
+            }
+            mEarlyCreatedUsers = null; // no longer needed
+
+            // Also do a one-time migration of all users to SP-based credentials with the CE key
+            // encrypted by the SP.  This is needed for the system user on the first boot of a
+            // device, as the system user is special and never goes through the user creation flow
+            // that other users do.  It is also needed for existing users on a device upgraded from
+            // Android 13 or earlier, where users with no LSKF didn't necessarily have an SP, and if
+            // they did have an SP then their CE key wasn't encrypted by it.
+            //
+            // If this gets interrupted (e.g. by the device powering off), there shouldn't be a
+            // problem since this will run again on the next boot, and setUserKeyProtection() is
+            // okay with the key being already protected by the given secret.
+            if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+                for (UserInfo user : mUserManager.getAliveUsers()) {
+                    removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
+                    synchronized (mSpManager) {
+                        migrateUserToSpWithBoundCeKeyLocked(user.id);
+                    }
+                }
+                setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+            }
+
+            mBootComplete = true;
+        }
+    }
+
+    @GuardedBy("mSpManager")
+    private void migrateUserToSpWithBoundCeKeyLocked(@UserIdInt int userId) {
+        if (isUserSecure(userId)) {
+            Slogf.d(TAG, "User %d is secured; no migration needed", userId);
+            return;
+        }
+        long protectorId = getCurrentLskfBasedProtectorId(userId);
+        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+            Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
+            initializeSyntheticPasswordLocked(userId);
+        } else {
+            Slogf.i(TAG, "Existing unsecured user %d has a synthetic password; re-encrypting CE " +
+                    "key with it", userId);
+            AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
+                    getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
+                    null);
+            if (result.syntheticPassword == null) {
+                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
+                return;
+            }
+            setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+        }
+    }
+
     /**
      * Returns the lowest password quality that still presents the same UI for entering it.
      *
@@ -1269,9 +1331,8 @@
      * can end up calling into other system services to process user unlock request (via
      * {@link com.android.server.SystemServiceManager#unlockUser} </em>
      */
-    private void unlockUser(int userId, byte[] secret) {
-        Slog.i(TAG, "Unlocking user " + userId + " with secret only, length "
-                + (secret != null ? secret.length : 0));
+    private void unlockUser(@UserIdInt int userId) {
+        Slogf.i(TAG, "Unlocking user %d", userId);
         // TODO: make this method fully async so we can update UI with progress strings
         final boolean alreadyUnlocked = mUserManager.isUserUnlockingOrUnlocked(userId);
         final CountDownLatch latch = new CountDownLatch(1);
@@ -1294,7 +1355,7 @@
         };
 
         try {
-            mActivityManager.unlockUser(userId, null, secret, listener);
+            mActivityManager.unlockUser2(userId, listener);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -1587,6 +1648,7 @@
                 if (!savedCredential.isNone()) {
                     throw new IllegalStateException("Saved credential given, but user has no SP");
                 }
+                // TODO(b/232452368): this case is only needed by unit tests now; remove it.
                 initializeSyntheticPasswordLocked(userId);
             } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
                 // get credential from keystore when profile has unified lock
@@ -1897,19 +1959,12 @@
         mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
     }
 
-    private void setUserKeyProtection(int userId, byte[] key) {
-        if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
-        addUserKeyAuth(userId, key);
-    }
-
-    private void clearUserKeyProtection(int userId, byte[] secret) {
-        if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
-        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+    private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
         final long callingId = Binder.clearCallingIdentity();
         try {
-            mStorageManager.clearUserKeyAuth(userId, userInfo.serialNumber, secret);
+            mStorageManager.setUserKeyProtection(userId, secret);
         } catch (RemoteException e) {
-            throw new IllegalStateException("clearUserKeyAuth failed user=" + userId);
+            throw new IllegalStateException("Failed to protect CE key for user " + userId, e);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -1924,40 +1979,51 @@
         }
     }
 
-    /** Unlock file-based encryption */
-    private void unlockUserKey(int userId, byte[] secret) {
+    /**
+     * Unlocks the user's CE (credential-encrypted) storage if it's not already unlocked.
+     * <p>
+     * This method doesn't throw exceptions because it is called opportunistically whenever a user
+     * is started.  Whether it worked or not can be detected by whether the key got unlocked or not.
+     */
+    private void unlockUserKey(@UserIdInt int userId, SyntheticPassword sp) {
+        if (isUserKeyUnlocked(userId)) {
+            Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
+            return;
+        }
         final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        final String userType = isUserSecure(userId) ? "secured" : "unsecured";
+        final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
             mStorageManager.unlockUserKey(userId, userInfo.serialNumber, secret);
+            Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
         } catch (RemoteException e) {
-            throw new IllegalStateException("Failed to unlock user key " + userId, e);
-
+            Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
+        } finally {
+            Arrays.fill(secret, (byte) 0);
         }
     }
 
-    private void addUserKeyAuth(int userId, byte[] secret) {
-        final UserInfo userInfo = mUserManager.getUserInfo(userId);
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            mStorageManager.addUserKeyAuth(userId, userInfo.serialNumber, secret);
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Failed to add new key to vold " + userId, e);
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-    }
-
-    private void fixateNewestUserKeyAuth(int userId) {
-        if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            mStorageManager.fixateNewestUserKeyAuth(userId);
-        } catch (RemoteException e) {
-            // OK to ignore the exception as vold would just accept both old and new
-            // keys if this call fails, and will fix itself during the next boot
-            Slog.w(TAG, "fixateNewestUserKeyAuth failed", e);
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
+    private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+        synchronized (mSpManager) {
+            if (isUserKeyUnlocked(userId)) {
+                Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
+                return;
+            }
+            if (isUserSecure(userId)) {
+                Slogf.d(TAG, "Not unlocking CE storage for user %d yet because user is secured",
+                        userId);
+                return;
+            }
+            Slogf.i(TAG, "Unwrapping synthetic password for unsecured user %d", userId);
+            AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
+                    getGateKeeperService(), getCurrentLskfBasedProtectorId(userId),
+                    LockscreenCredential.createNone(), userId, null);
+            if (result.syntheticPassword == null) {
+                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
+                return;
+            }
+            onSyntheticPasswordKnown(userId, result.syntheticPassword);
+            unlockUserKey(userId, result.syntheticPassword);
         }
     }
 
@@ -2228,8 +2294,50 @@
         });
     }
 
-    private void removeUser(int userId, boolean unknownUser) {
-        Slog.i(TAG, "RemoveUser: " + userId);
+    private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
+        synchronized (mUserCreationAndRemovalLock) {
+            // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
+            // rather automatically delay it to later.  We do this because protecting the synthetic
+            // password requires the Weaver HAL if the device supports it, and some devices don't
+            // make Weaver available until fairly late in the boot process.  This logic ensures a
+            // consistent flow across all devices, regardless of their Weaver implementation.
+            if (!mBootComplete) {
+                Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
+                        userId);
+                mEarlyCreatedUsers.put(userId, userSerialNumber);
+                mEarlyRemovedUsers.delete(userId);
+                return;
+            }
+            removeStateForReusedUserIdIfNecessary(userId, userSerialNumber);
+            synchronized (mSpManager) {
+                initializeSyntheticPasswordLocked(userId);
+            }
+        }
+    }
+
+    private void removeUser(@UserIdInt int userId) {
+        synchronized (mUserCreationAndRemovalLock) {
+            // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
+            // automatically delay it to later.  We do this because deleting synthetic password
+            // protectors requires the Weaver HAL if the device supports it, and some devices don't
+            // make Weaver available until fairly late in the boot process.  This logic ensures a
+            // consistent flow across all devices, regardless of their Weaver implementation.
+            if (!mBootComplete) {
+                Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
+                        userId);
+                if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
+                    mEarlyCreatedUsers.delete(userId);
+                } else {
+                    mEarlyRemovedUsers.put(userId, -1 /* unused */);
+                }
+                return;
+            }
+            Slogf.i(TAG, "Removing state for user %d", userId);
+            removeUserState(userId);
+        }
+    }
+
+    private void removeUserState(@UserIdInt int userId) {
         removeBiometricsForUser(userId);
         mSpManager.removeUser(getGateKeeperService(), userId);
         mStrongAuth.removeUser(userId);
@@ -2238,11 +2346,9 @@
         mManagedProfilePasswordCache.removePassword(userId);
 
         gateKeeperClearSecureUserId(userId);
-        if (unknownUser || isCredentialSharableWithParent(userId)) {
-            removeKeystoreProfileKey(userId);
-        }
-        // Clean up storage last, this is to ensure that cleanupDataForReusedUserIdIfNecessary()
-        // can make the assumption that no USER_SERIAL_NUMBER_KEY means user is fully removed.
+        removeKeystoreProfileKey(userId);
+        // Clean up storage last, so that removeStateForReusedUserIdIfNecessary() can assume that no
+        // USER_SERIAL_NUMBER_KEY means user is fully removed.
         mStorage.removeUser(userId);
     }
 
@@ -2497,8 +2603,11 @@
     }
 
     private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
-        // Pass the primary user's auth secret to the HAL
-        if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
+        // If the given user is the primary user, pass the auth secret to the HAL.  Only the system
+        // user can be primary.  Check for the system user ID before calling getUserInfo(), as other
+        // users may still be under construction.
+        if (mAuthSecretService != null && userId == UserHandle.USER_SYSTEM &&
+                mUserManager.getUserInfo(userId).isPrimary()) {
             try {
                 final byte[] rawSecret = sp.deriveVendorAuthSecret();
                 final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
@@ -2513,9 +2622,13 @@
     }
 
     /**
-     * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
-     * This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
-     * or when an escrow token is activated on a device with an empty LSKF.
+     * Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
+     * protects the user's CE key with a key derived from the SP.
+     * <p>
+     * This is called just once in the lifetime of the user: at user creation time (possibly delayed
+     * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
+     * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
+     * necessarily have an SP.
      */
     @GuardedBy("mSpManager")
     @VisibleForTesting
@@ -2529,6 +2642,7 @@
         final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                 LockscreenCredential.createNone(), sp, userId);
         setCurrentLskfBasedProtectorId(protectorId, userId);
+        setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
         onSyntheticPasswordKnown(userId, sp);
         return sp;
     }
@@ -2601,11 +2715,10 @@
 
         unlockKeystore(sp.deriveKeyStorePassword(), userId);
 
-        {
-            final byte[] secret = sp.deriveFileBasedEncryptionKey();
-            unlockUser(userId, secret);
-            Arrays.fill(secret, (byte) 0);
-        }
+        unlockUserKey(userId, sp);
+
+        unlockUser(userId);
+
         activateEscrowTokens(sp, userId);
 
         if (isProfileWithSeparatedLock(userId)) {
@@ -2626,9 +2739,9 @@
      * be empty) and replacing the old LSKF-based protector with it.  The SP itself is not changed.
      *
      * Also maintains the invariants described in {@link SyntheticPasswordManager} by
-     * setting/clearing the protection (by the SP) on the user's file-based encryption key and
-     * auth-bound Keystore keys when the LSKF is added/removed, respectively.  If the new LSKF is
-     * nonempty, then the Gatekeeper auth token is also refreshed.
+     * 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
+     * token is also refreshed.
      */
     @GuardedBy("mSpManager")
     private long setLockCredentialWithSpLocked(LockscreenCredential credential,
@@ -2648,8 +2761,6 @@
             } else {
                 mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
                 mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
-                setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
-                fixateNewestUserKeyAuth(userId);
                 setKeystorePassword(sp.deriveKeyStorePassword(), userId);
             }
         } else {
@@ -2659,9 +2770,7 @@
 
             mSpManager.clearSidForUser(userId);
             gateKeeperClearSecureUserId(userId);
-            unlockUserKey(userId, sp.deriveFileBasedEncryptionKey());
-            clearUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
-            fixateNewestUserKeyAuth(userId);
+            unlockUserKey(userId, sp);
             unlockKeystore(sp.deriveKeyStorePassword(), userId);
             setKeystorePassword(null, userId);
             removeBiometricsForUser(userId);
@@ -2804,6 +2913,7 @@
             if (!isUserSecure(userId)) {
                 long protectorId = getCurrentLskfBasedProtectorId(userId);
                 if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+                    // TODO(b/232452368): this case is only needed by unit tests now; remove it.
                     sp = initializeSyntheticPasswordLocked(userId);
                 } else {
                     sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
@@ -2888,7 +2998,7 @@
                 // If clearing credential, unlock the user manually in order to progress user start
                 // Call unlockUser() on a handler thread so no lock is held (either by LSS or by
                 // the caller like DPMS), otherwise it can lead to deadlock.
-                mHandler.post(() -> unlockUser(userId, null));
+                mHandler.post(() -> unlockUser(userId));
             }
             notifyPasswordChanged(credential, userId);
             notifySeparateProfileChallengeChanged(userId);
@@ -2940,6 +3050,7 @@
 
     @Override
     public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
+        checkPasswordReadPermission();
         try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
             if (cred == null) {
                 return false;
@@ -2951,6 +3062,7 @@
 
     @Override
     public void removeCachedUnifiedChallenge(int userId) {
+        checkWritePermission();
         mManagedProfilePasswordCache.removePassword(userId);
     }
 
@@ -3039,6 +3151,9 @@
         pw.decreaseIndent();
 
         pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
+        synchronized (mUserCreationAndRemovalLock) {
+            pw.println("BootComplete: " + mBootComplete);
+        }
     }
 
     private void dumpKeystoreKeys(IndentingPrintWriter pw) {
@@ -3195,6 +3310,21 @@
     private final class LocalService extends LockSettingsInternal {
 
         @Override
+        public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+            LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
+        }
+
+        @Override
+        public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
+            LockSettingsService.this.createNewUser(userId, userSerialNumber);
+        }
+
+        @Override
+        public void removeUser(@UserIdInt int userId) {
+            LockSettingsService.this.removeUser(userId);
+        }
+
+        @Override
         public long addEscrowToken(byte[] token, int userId,
                 EscrowTokenStateChangeCallback callback) {
             return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS
index 7577ee5..55b0cff 100644
--- a/services/core/java/com/android/server/locksettings/OWNERS
+++ b/services/core/java/com/android/server/locksettings/OWNERS
@@ -1,4 +1,3 @@
+ebiggers@google.com
 jaggies@google.com
-kchyn@google.com
 rubinxu@google.com
-xunchang@google.com
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 2a6ae44..cb6e43c 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -17,7 +17,6 @@
 package com.android.server.locksettings;
 
 import android.security.AndroidKeyStoreMaintenance;
-import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
@@ -223,13 +222,6 @@
                 keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
                 Slog.i(TAG, "Using rollback-resistant key");
             } catch (KeyStoreException e) {
-                if (!(e.getCause() instanceof android.security.KeyStoreException)) {
-                    throw e;
-                }
-                int errorCode = ((android.security.KeyStoreException) e.getCause()).getErrorCode();
-                if (errorCode != KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE) {
-                    throw e;
-                }
                 Slog.w(TAG, "Rollback-resistant keys unavailable.  Falling back to "
                         + "non-rollback-resistant key");
                 keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index f1afb96..66bdadb 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -49,6 +49,7 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.utils.Slogf;
 
 import libcore.util.HexEncoding;
 
@@ -79,11 +80,12 @@
  *    LockscreenCredential.  The LSKF may be empty (none).  There may be escrow token-based
  *    protectors as well, only for specific use cases such as enterprise-managed users.
  *
- *  - While the user's LSKF is nonempty, the SP protects the user's CE (credential encrypted)
- *    storage and auth-bound Keystore keys: the user's CE key is encrypted by an SP-derived secret,
- *    and the user's Keystore and Gatekeeper passwords are other SP-derived secrets.  However, while
- *    the user's LSKF is empty, these protections are cleared; this is needed to invalidate the
- *    auth-bound keys and make UserController.unlockUser() work with an empty secret.
+ *  - The user's credential-encrypted storage is always protected by the SP.
+ *
+ *  - The user's auth-bound Keystore keys are protected by the SP, but only while an LSKF is set.
+ *    This works by setting the user's Keystore and Gatekeeper passwords to SP-derived secrets, but
+ *    only while an LSKF is set.  When the LSKF is removed, these passwords are cleared,
+ *    invalidating the user's auth-bound keys.
  *
  * Files stored on disk for each user:
  *   For the SP itself, stored under NULL_PROTECTOR_ID:
@@ -1026,6 +1028,14 @@
             long protectorId, @NonNull LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
         AuthenticationResult result = new AuthenticationResult();
+
+        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+            // This should never happen, due to the migration done in LSS.bootCompleted().
+            Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
+            result.gkResponse = VerifyCredentialResponse.ERROR;
+            return result;
+        }
+
         PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
                     userId));
 
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index cbbb336..fdc5bab 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -40,6 +40,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.internal.app.LogAccessDialogActivity;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -101,7 +103,7 @@
     private final Injector mInjector;
     private final Supplier<Long> mClock;
     private final BinderService mBinderService;
-    private final LogcatManagerServiceInternal mLocalService;
+    private final LogAccessDialogCallback mDialogCallback;
     private final Handler mHandler;
     private ActivityManagerInternal mActivityManagerInternal;
     private ILogd mLogdService;
@@ -206,7 +208,8 @@
         }
     }
 
-    final class LogcatManagerServiceInternal {
+    final class LogAccessDialogCallback extends ILogAccessDialogCallback.Stub {
+        @Override
         public void approveAccessForClient(int uid, @NonNull String packageName) {
             final LogAccessClient client = new LogAccessClient(uid, packageName);
             if (DEBUG) {
@@ -216,6 +219,7 @@
             mHandler.sendMessageAtTime(msg, mClock.get());
         }
 
+        @Override
         public void declineAccessForClient(int uid, @NonNull String packageName) {
             final LogAccessClient client = new LogAccessClient(uid, packageName);
             if (DEBUG) {
@@ -302,7 +306,7 @@
         mInjector = injector;
         mClock = injector.createClock();
         mBinderService = new BinderService();
-        mLocalService = new LogcatManagerServiceInternal();
+        mDialogCallback = new LogAccessDialogCallback();
         mHandler = new LogAccessRequestHandler(injector.getLooper(), this);
     }
 
@@ -311,15 +315,14 @@
         try {
             mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
             publishBinderService("logcat", mBinderService);
-            publishLocalService(LogcatManagerServiceInternal.class, mLocalService);
         } catch (Throwable t) {
             Slog.e(TAG, "Could not start the LogcatManagerService.", t);
         }
     }
 
     @VisibleForTesting
-    LogcatManagerServiceInternal getLocalService() {
-        return mLocalService;
+    LogAccessDialogCallback getDialogCallback() {
+        return mDialogCallback;
     }
 
     @VisibleForTesting
@@ -438,6 +441,7 @@
         mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client),
                 mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
         final Intent mIntent = createIntent(client);
+        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
     }
 
@@ -538,6 +542,7 @@
 
         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
         intent.putExtra(Intent.EXTRA_UID, client.mUid);
+        intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder());
 
         return intent;
     }
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 9a19031..b1e8b40 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -246,10 +246,13 @@
 
     @Override
     public String toString() {
-        if (mPendingIntent != null) {
-            return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}";
-        }
-        return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}";
+        StringBuilder sb = new StringBuilder();
+        sb.append("MBR {pi=").append(mPendingIntent);
+        sb.append(", componentName=").append(mComponentName);
+        sb.append(", type=").append(mComponentType);
+        sb.append(", pkg=").append(mPackageName);
+        sb.append("}");
+        return sb.toString();
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index b688e09..c8697b4 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -41,6 +41,8 @@
 final class MediaRoute2ProviderWatcher {
     private static final String TAG = "MR2ProviderWatcher";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE =
+            PackageManager.ResolveInfoFlags.of(0);
 
     private final Context mContext;
     private final Callback mCallback;
@@ -110,8 +112,9 @@
         // Reorder the list so that providers left at the end will be the ones to remove.
         int targetIndex = 0;
         Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
-        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
-                intent, 0, mUserId)) {
+        for (ResolveInfo resolveInfo :
+                mPackageManager.queryIntentServicesAsUser(
+                        intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) {
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo != null) {
                 int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index bfa8af9..1dd7965 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -58,6 +58,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -87,6 +89,7 @@
     private static final int PACKAGE_IMPORTANCE_FOR_DISCOVERY = IMPORTANCE_FOREGROUND_SERVICE;
 
     private final Context mContext;
+    private final UserManagerInternal mUserManagerInternal;
     private final Object mLock = new Object();
     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
     final ActivityManager mActivityManager;
@@ -99,7 +102,7 @@
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
     @GuardedBy("mLock")
-    private int mCurrentUserId = -1;
+    private int mCurrentActiveUserId = -1;
 
     private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
             (uid, importance) -> {
@@ -125,12 +128,13 @@
         }
     };
 
-    MediaRouter2ServiceImpl(Context context) {
+    /* package */ MediaRouter2ServiceImpl(Context context) {
         mContext = context;
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
                 PACKAGE_IMPORTANCE_FOR_DISCOVERY);
         mPowerManager = mContext.getSystemService(PowerManager.class);
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
         IntentFilter screenOnOffIntentFilter = new IntentFilter();
         screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
@@ -601,25 +605,43 @@
         }
     }
 
-    //TODO(b/136703681): Review this is handling multi-user properly.
-    void switchUser() {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "MediaRouter2ServiceImpl");
+
+        String indent = prefix + "  ";
+
         synchronized (mLock) {
-            int userId = ActivityManager.getCurrentUser();
-            if (mCurrentUserId != userId) {
-                final int oldUserId = mCurrentUserId;
-                mCurrentUserId = userId; // do this first
+            pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
+            pw.println(indent + "mCurrentActiveUserId=" + mCurrentActiveUserId);
 
-                UserRecord oldUser = mUserRecords.get(oldUserId);
-                if (oldUser != null) {
-                    oldUser.mHandler.sendMessage(
-                            obtainMessage(UserHandler::stop, oldUser.mHandler));
-                    disposeUserIfNeededLocked(oldUser); // since no longer current user
+            pw.println(indent + "UserRecords:");
+            if (mUserRecords.size() > 0) {
+                for (int i = 0; i < mUserRecords.size(); i++) {
+                    mUserRecords.valueAt(i).dump(pw, indent + "  ");
                 }
+            } else {
+                pw.println(indent + "<no user records>");
+            }
+        }
+    }
 
-                UserRecord newUser = mUserRecords.get(userId);
-                if (newUser != null) {
-                    newUser.mHandler.sendMessage(
-                            obtainMessage(UserHandler::start, newUser.mHandler));
+    /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
+        synchronized (mLock) {
+            if (mCurrentActiveUserId != newActiveUserId) {
+                mCurrentActiveUserId = newActiveUserId;
+                for (int i = 0; i < mUserRecords.size(); i++) {
+                    int userId = mUserRecords.keyAt(i);
+                    UserRecord userRecord = mUserRecords.valueAt(i);
+                    if (isUserActiveLocked(userId)) {
+                        // userId corresponds to the active user, or one of its profiles. We
+                        // ensure the associated structures are initialized.
+                        userRecord.mHandler.sendMessage(
+                                obtainMessage(UserHandler::start, userRecord.mHandler));
+                    } else {
+                        userRecord.mHandler.sendMessage(
+                                obtainMessage(UserHandler::stop, userRecord.mHandler));
+                        disposeUserIfNeededLocked(userRecord);
+                    }
                 }
             }
         }
@@ -637,11 +659,21 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
+     * of the active user, returns {@code false} otherwise.
+     */
+    @GuardedBy("mLock")
+    private boolean isUserActiveLocked(int userId) {
+        return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
+    }
+
     ////////////////////////////////////////////////////////////////
     ////  ***Locked methods related to MediaRouter2
     ////   - Should have @NonNull/@Nullable on all arguments
     ////////////////////////////////////////////////////////////////
 
+    @GuardedBy("mLock")
     private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
             @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
             boolean hasModifyAudioRoutingPermission) {
@@ -669,6 +701,7 @@
                         userRecord.mHandler, routerRecord));
     }
 
+    @GuardedBy("mLock")
     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
         RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
         if (routerRecord == null) {
@@ -891,6 +924,7 @@
         return sessionInfos;
     }
 
+    @GuardedBy("mLock")
     private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
             int uid, int pid, @NonNull String packageName, int userId) {
         final IBinder binder = manager.asBinder();
@@ -1126,13 +1160,14 @@
     ////   - Should have @NonNull/@Nullable on all arguments
     ////////////////////////////////////////////////////////////
 
+    @GuardedBy("mLock")
     private UserRecord getOrCreateUserRecordLocked(int userId) {
         UserRecord userRecord = mUserRecords.get(userId);
         if (userRecord == null) {
             userRecord = new UserRecord(userId);
             mUserRecords.put(userId, userRecord);
             userRecord.init();
-            if (userId == mCurrentUserId) {
+            if (isUserActiveLocked(userId)) {
                 userRecord.mHandler.sendMessage(
                         obtainMessage(UserHandler::start, userRecord.mHandler));
             }
@@ -1140,12 +1175,13 @@
         return userRecord;
     }
 
+    @GuardedBy("mLock")
     private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
         // If there are no records left and the user is no longer current then go ahead
         // and purge the user record and all of its associated state.  If the user is current
         // then leave it alone since we might be connected to a route or want to query
         // the same route information again soon.
-        if (userRecord.mUserId != mCurrentUserId
+        if (!isUserActiveLocked(userRecord.mUserId)
                 && userRecord.mRouterRecords.isEmpty()
                 && userRecord.mManagerRecords.isEmpty()) {
             if (DEBUG) {
@@ -1197,6 +1233,38 @@
             }
             return null;
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "UserRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mUserId=" + mUserId);
+
+            pw.println(indent + "Router Records:");
+            if (!mRouterRecords.isEmpty()) {
+                for (RouterRecord routerRecord : mRouterRecords) {
+                    routerRecord.dump(pw, indent + "  ");
+                }
+            } else {
+                pw.println(indent + "<no router records>");
+            }
+
+            pw.println(indent + "Manager Records:");
+            if (!mManagerRecords.isEmpty()) {
+                for (ManagerRecord managerRecord : mManagerRecords) {
+                    managerRecord.dump(pw, indent + "  ");
+                }
+            } else {
+                pw.println(indent + "<no manager records>");
+            }
+
+            mCompositeDiscoveryPreference.dump(pw, indent);
+
+            if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
+                pw.println(indent + "<could not dump handler state>");
+            }
+        }
     }
 
     final class RouterRecord implements IBinder.DeathRecipient {
@@ -1211,7 +1279,6 @@
         public final int mRouterId;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
-        public MediaRoute2Info mSelectedRoute;
 
         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
                 String packageName, boolean hasConfigureWifiDisplayPermission,
@@ -1236,6 +1303,24 @@
         public void binderDied() {
             routerDied(this);
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "RouterRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mPackageName=" + mPackageName);
+            pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
+            pw.println(indent + "mUid=" + mUid);
+            pw.println(indent + "mPid=" + mPid);
+            pw.println(indent + "mHasConfigureWifiDisplayPermission="
+                    + mHasConfigureWifiDisplayPermission);
+            pw.println(indent + "mHasModifyAudioRoutingPermission="
+                    + mHasModifyAudioRoutingPermission);
+            pw.println(indent + "mRouterId=" + mRouterId);
+
+            mDiscoveryPreference.dump(pw, indent);
+        }
     }
 
     final class ManagerRecord implements IBinder.DeathRecipient {
@@ -1267,8 +1352,20 @@
             managerDied(this);
         }
 
-        public void dump(PrintWriter pw, String prefix) {
-            pw.println(prefix + this);
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "ManagerRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mPackageName=" + mPackageName);
+            pw.println(indent + "mManagerId=" + mManagerId);
+            pw.println(indent + "mUid=" + mUid);
+            pw.println(indent + "mPid=" + mPid);
+            pw.println(indent + "mIsScanning=" + mIsScanning);
+
+            if (mLastSessionCreationRequest != null) {
+                mLastSessionCreationRequest.dump(pw, indent);
+            }
         }
 
         public void startScan() {
@@ -1455,6 +1552,15 @@
             }
         }
 
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "UserHandler");
+
+            String indent = prefix + "  ";
+            pw.println(indent + "mRunning=" + mRunning);
+
+            mWatcher.dump(pw, prefix);
+        }
+
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
 
@@ -2340,5 +2446,16 @@
             mOldSession = oldSession;
             mRoute = route;
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "SessionCreationRequest");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
+            pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
+            mOldSession.dump(pw, indent);
+            mRoute.dump(pw, prefix);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4806b52..511026e 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -17,7 +17,9 @@
 package com.android.server.media;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
+import android.app.UserSwitchObserver;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
@@ -61,7 +63,9 @@
 import android.util.TimeUtils;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
 import com.android.server.Watchdog;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -102,9 +106,10 @@
     // State guarded by mLock.
     private final Object mLock = new Object();
 
+    private final UserManagerInternal mUserManagerInternal;
     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
-    private int mCurrentUserId = -1;
+    private int mCurrentActiveUserId = -1;
     private final IAudioService mAudioService;
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final Handler mHandler = new Handler();
@@ -130,6 +135,7 @@
         mBluetoothA2dpRouteId =
                 res.getString(com.android.internal.R.string.bluetooth_a2dp_audio_route_id);
 
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(context);
@@ -146,18 +152,27 @@
         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
     }
 
-    public void systemRunning() {
-        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
-                    switchUser();
-                }
-            }
-        }, filter);
-
-        switchUser();
+    /**
+     * Initializes the MediaRouter service.
+     *
+     * @throws RemoteException If an error occurs while registering the {@link UserSwitchObserver}.
+     */
+    @RequiresPermission(
+            anyOf = {
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.INTERACT_ACROSS_USERS_FULL"
+            })
+    public void systemRunning() throws RemoteException {
+        ActivityManager.getService()
+                .registerUserSwitchObserver(
+                        new UserSwitchObserver() {
+                            @Override
+                            public void onUserSwitchComplete(int newUserId) {
+                                updateRunningUserAndProfiles(newUserId);
+                            }
+                        },
+                        TAG);
+        updateRunningUserAndProfiles(ActivityManager.getCurrentUser());
     }
 
     @Override
@@ -377,7 +392,7 @@
         pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
         pw.println();
         pw.println("Global state");
-        pw.println("  mCurrentUserId=" + mCurrentUserId);
+        pw.println("  mCurrentUserId=" + mCurrentActiveUserId);
 
         synchronized (mLock) {
             final int count = mUserRecords.size();
@@ -387,6 +402,9 @@
                 userRecord.dump(pw, "");
             }
         }
+
+        pw.println();
+        mService2.dump(pw, "");
     }
 
     // Binder call
@@ -631,26 +649,31 @@
         }
     }
 
-    void switchUser() {
+    /**
+     * Starts all {@link UserRecord user records} associated with the active user (whose ID is
+     * {@code newActiveUserId}) or the active user's profiles.
+     *
+     * <p>All other records are stopped, and those without associated client records are removed.
+     */
+    private void updateRunningUserAndProfiles(int newActiveUserId) {
         synchronized (mLock) {
-            int userId = ActivityManager.getCurrentUser();
-            if (mCurrentUserId != userId) {
-                final int oldUserId = mCurrentUserId;
-                mCurrentUserId = userId; // do this first
-
-                UserRecord oldUser = mUserRecords.get(oldUserId);
-                if (oldUser != null) {
-                    oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
-                    disposeUserIfNeededLocked(oldUser); // since no longer current user
-                }
-
-                UserRecord newUser = mUserRecords.get(userId);
-                if (newUser != null) {
-                    newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+            if (mCurrentActiveUserId != newActiveUserId) {
+                mCurrentActiveUserId = newActiveUserId;
+                for (int i = 0; i < mUserRecords.size(); i++) {
+                    int userId = mUserRecords.keyAt(i);
+                    UserRecord userRecord = mUserRecords.valueAt(i);
+                    if (isUserActiveLocked(userId)) {
+                        // userId corresponds to the active user, or one of its profiles. We
+                        // ensure the associated structures are initialized.
+                        userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+                    } else {
+                        userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+                        disposeUserIfNeededLocked(userRecord);
+                    }
                 }
             }
         }
-        mService2.switchUser();
+        mService2.updateRunningUserAndProfiles(newActiveUserId);
     }
 
     void clientDied(ClientRecord clientRecord) {
@@ -705,8 +728,10 @@
         clientRecord.mGroupId = groupId;
         if (groupId != null) {
             userRecord.addToGroup(groupId, clientRecord);
-            userRecord.mHandler.obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId)
-                .sendToTarget();
+            userRecord
+                    .mHandler
+                    .obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId)
+                    .sendToTarget();
         }
     }
 
@@ -792,9 +817,13 @@
                                 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
                         if (group != null) {
                             group.mSelectedRouteId = routeId;
-                            clientRecord.mUserRecord.mHandler.obtainMessage(
-                                UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, clientRecord.mGroupId)
-                                .sendToTarget();
+                            clientRecord
+                                    .mUserRecord
+                                    .mHandler
+                                    .obtainMessage(
+                                            UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED,
+                                            clientRecord.mGroupId)
+                                    .sendToTarget();
                         }
                     }
                 }
@@ -826,7 +855,7 @@
         if (DEBUG) {
             Slog.d(TAG, userRecord + ": Initialized");
         }
-        if (userRecord.mUserId == mCurrentUserId) {
+        if (isUserActiveLocked(userRecord.mUserId)) {
             userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
         }
     }
@@ -836,8 +865,7 @@
         // and purge the user record and all of its associated state.  If the user is current
         // then leave it alone since we might be connected to a route or want to query
         // the same route information again soon.
-        if (userRecord.mUserId != mCurrentUserId
-                && userRecord.mClientRecords.isEmpty()) {
+        if (!isUserActiveLocked(userRecord.mUserId) && userRecord.mClientRecords.isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, userRecord + ": Disposed");
             }
@@ -846,6 +874,14 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
+     * of the active user, returns {@code false} otherwise.
+     */
+    private boolean isUserActiveLocked(int userId) {
+        return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
+    }
+
     private void initializeClientLocked(ClientRecord clientRecord) {
         if (DEBUG) {
             Slog.d(TAG, clientRecord + ": Registered");
@@ -885,7 +921,10 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
-                BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
+                BluetoothDevice btDevice =
+                        intent.getParcelableExtra(
+                                BluetoothDevice.EXTRA_DEVICE,
+                                android.bluetooth.BluetoothDevice.class);
                 synchronized (mLock) {
                     mActiveBluetoothDevice = btDevice;
                     mGlobalBluetoothA2dpOn = btDevice != null;
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 8e944b7..56390a9 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -217,6 +217,7 @@
     synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
         final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
         Connection c = null;
+        int iteratorId = -1;
         try {
             c = connect();
             final IIdmap2 service = c.getIdmap2();
@@ -225,9 +226,9 @@
                 return Collections.emptyList();
             }
 
-            service.acquireFabricatedOverlayIterator();
+            iteratorId = service.acquireFabricatedOverlayIterator();
             List<FabricatedOverlayInfo> infos;
-            while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) {
+            while (!(infos = service.nextFabricatedOverlayInfos(iteratorId)).isEmpty()) {
                 allInfos.addAll(infos);
             }
             return allInfos;
@@ -235,8 +236,8 @@
             Slog.wtf(TAG, "failed to get all fabricated overlays", e);
         } finally {
             try {
-                if (c.getIdmap2() != null) {
-                    c.getIdmap2().releaseFabricatedOverlayIterator();
+                if (c.getIdmap2() != null && iteratorId != -1) {
+                    c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
                 }
             } catch (RemoteException e) {
                 // ignore
@@ -267,8 +268,8 @@
         try {
             SystemService.start(IDMAP_DAEMON);
         } catch (RuntimeException e) {
+            Slog.wtf(TAG, "Failed to enable idmap2 daemon", e);
             if (e.getMessage().contains("failed to set system property")) {
-                Slog.w(TAG, "Failed to enable idmap2 daemon", e);
                 return null;
             }
         }
diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java
index 4dd9c49..672ae2e 100644
--- a/services/core/java/com/android/server/pm/ApexPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ApexPackageInfo.java
@@ -329,17 +329,15 @@
             ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
 
             if (throwable == null) {
-                // Calling hideAsFinal to assign derived fields for the app info flags.
-                parseResult.parsedPackage.hideAsFinal();
-
                 // TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire
                 //  calling path code
                 ScanPackageUtils.applyPolicy(parseResult.parsedPackage,
                         PackageManagerService.SCAN_AS_SYSTEM,
                         mPackageManager == null ? null : mPackageManager.getPlatformPackage(),
                         false);
-                results.add(new ApexManager.ScanResult(
-                        ai, parseResult.parsedPackage, parseResult.parsedPackage.getPackageName()));
+                // Calling hideAsFinal to assign derived fields for the app info flags.
+                AndroidPackage finalPkg = parseResult.parsedPackage.hideAsFinal();
+                results.add(new ApexManager.ScanResult(ai, finalPkg, finalPkg.getPackageName()));
             } else if (throwable instanceof PackageManagerException) {
                 throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
             } else {
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index a286160..9e93fe0 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -650,7 +650,7 @@
         // Skip /product folder.
         // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules.
         if (!containsFile(Environment.getProductDirectory(), filePath)) {
-            byte[] verityHash = VerityUtils.getFsverityRootHash(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/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 0f5d8fd..b8e1e9a 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -298,7 +298,8 @@
             // Create a native library symlink only if we have native libraries
             // and if the native libraries are 32 bit libraries. We do not provide
             // this symlink for 64 bit libraries.
-            String primaryCpuAbi = AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting);
+            String primaryCpuAbi = pkgSetting == null
+                    ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg) : pkgSetting.getPrimaryCpuAbi();
             if (primaryCpuAbi != null && !VMRuntime.is64BitAbi(primaryCpuAbi)) {
                 final String nativeLibPath = pkg.getNativeLibraryDir();
                 if (!(new File(nativeLibPath).exists())) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 14140b5..3b676c65 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,7 +28,6 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -199,6 +198,7 @@
     protected SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
 
     protected volatile boolean mCacheReady = false;
+    protected volatile boolean mCacheEnabled = true;
 
     protected static final boolean CACHE_VALID = true;
     protected static final boolean CACHE_INVALID = false;
@@ -216,12 +216,12 @@
         return mQueriesViaComponent.contains(callingAppId, targetAppId);
     }
 
-    protected boolean isImplicitlyQueryable(int callingAppId, int targetAppId) {
-        return mImplicitlyQueryable.contains(callingAppId, targetAppId);
+    protected boolean isImplicitlyQueryable(int callingUid, int targetUid) {
+        return mImplicitlyQueryable.contains(callingUid, targetUid);
     }
 
-    protected boolean isRetainedImplicitlyQueryable(int callingAppId, int targetAppId) {
-        return mRetainedImplicitlyQueryable.contains(callingAppId, targetAppId);
+    protected boolean isRetainedImplicitlyQueryable(int callingUid, int targetUid) {
+        return mRetainedImplicitlyQueryable.contains(callingUid, targetUid);
     }
 
     protected boolean isQueryableViaUsesLibrary(int callingAppId, int targetAppId) {
@@ -337,13 +337,14 @@
                     || callingAppId == targetPkgSetting.getAppId()) {
                 return false;
             } else if (Process.isSdkSandboxUid(callingAppId)) {
+                final int targetAppId = targetPkgSetting.getAppId();
+                final int targetUid = UserHandle.getUid(userId, targetAppId);
                 // we only allow sdk sandbox processes access to forcequeryable packages
                 return !isForceQueryable(targetPkgSetting.getAppId())
-                      && !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
+                      && !isImplicitlyQueryable(callingUid, targetUid);
             }
             // use cache
-            if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
-                    true)) {
+            if (mCacheReady && mCacheEnabled) {
                 if (!shouldFilterApplicationUsingCache(callingUid,
                         targetPkgSetting.getAppId(),
                         userId)) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 79d72a3..4c21195 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -36,6 +36,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -223,6 +224,12 @@
                 return new AppsFilterSnapshotImpl(AppsFilterImpl.this);
             }
         };
+        readCacheEnabledSysProp();
+        SystemProperties.addChangeCallback(this::readCacheEnabledSysProp);
+    }
+
+    private void readCacheEnabledSysProp() {
+        mCacheEnabled = SystemProperties.getBoolean("debug.pm.use_app_filter_cache", true);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/AppsFilterLocked.java b/services/core/java/com/android/server/pm/AppsFilterLocked.java
index 870f9da..29bb14e 100644
--- a/services/core/java/com/android/server/pm/AppsFilterLocked.java
+++ b/services/core/java/com/android/server/pm/AppsFilterLocked.java
@@ -66,16 +66,16 @@
     }
 
     @Override
-    protected boolean isImplicitlyQueryable(int callingAppId, int targetAppId) {
+    protected boolean isImplicitlyQueryable(int callingUid, int targetUid) {
         synchronized (mImplicitlyQueryableLock) {
-            return super.isImplicitlyQueryable(callingAppId, targetAppId);
+            return super.isImplicitlyQueryable(callingUid, targetUid);
         }
     }
 
     @Override
-    protected boolean isRetainedImplicitlyQueryable(int callingAppId, int targetAppId) {
+    protected boolean isRetainedImplicitlyQueryable(int callingUid, int targetUid) {
         synchronized (mImplicitlyQueryableLock) {
-            return super.isRetainedImplicitlyQueryable(callingAppId, targetAppId);
+            return super.isRetainedImplicitlyQueryable(callingUid, targetUid);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
index 019c853..4e268a2 100644
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
@@ -74,6 +74,7 @@
             // cache is not ready, use an empty cache for the snapshot
             mShouldFilterCache = new WatchedSparseBooleanMatrix();
         }
+        mCacheEnabled = orig.mCacheEnabled;
         mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
 
         mBackgroundHandler = null;
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 5916196..15cd639 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -187,6 +187,8 @@
 
     PackageStateInternal getPackageStateInternal(String packageName);
     PackageStateInternal getPackageStateInternal(String packageName, int callingUid);
+    PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+            @UserIdInt int userId);
     ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId);
     ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
             int sourceUserId, int targetUserId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 3211ca1..ed846db 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1674,8 +1674,8 @@
             ApplicationInfo ai = new ApplicationInfo();
             ai.packageName = ps.getPackageName();
             ai.uid = UserHandle.getUid(userId, ps.getAppId());
-            ai.primaryCpuAbi = ps.getPrimaryCpuAbi();
-            ai.secondaryCpuAbi = ps.getSecondaryCpuAbi();
+            ai.primaryCpuAbi = ps.getPrimaryCpuAbiLegacy();
+            ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
             ai.setVersionCode(ps.getVersionCode());
             ai.flags = ps.getFlags();
             ai.privateFlags = ps.getPrivateFlags();
@@ -1816,6 +1816,19 @@
         return mSettings.getPackage(packageName);
     }
 
+    @Override
+    public PackageStateInternal getPackageStateFiltered(@NonNull String packageName,
+            int callingUid, @UserIdInt int userId) {
+        packageName = resolveInternalPackageNameInternalLocked(
+                packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
+        var packageState = mSettings.getPackage(packageName);
+        if (shouldFilterApplication(packageState, callingUid, userId)) {
+            return null;
+        } else {
+            return packageState;
+        }
+    }
+
     public final ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         if (getInstantAppPackageName(callingUid) != null) {
@@ -2161,7 +2174,7 @@
     }
 
     public final String resolveExternalPackageName(AndroidPackage pkg) {
-        if (pkg.getStaticSharedLibName() != null) {
+        if (pkg.getStaticSharedLibraryName() != null) {
             return pkg.getManifestPackageName();
         }
         return pkg.getPackageName();
@@ -2378,7 +2391,7 @@
         }
 
         final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
-                ps.getPkg().getStaticSharedLibName(), ps.getPkg().getStaticSharedLibVersion());
+                ps.getPkg().getStaticSharedLibraryName(), ps.getPkg().getStaticSharedLibVersion());
         if (libraryInfo == null) {
             return false;
         }
@@ -2434,7 +2447,7 @@
         }
 
         final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
-                ps.getPkg().getSdkLibName(), ps.getPkg().getSdkLibVersionMajor());
+                ps.getPkg().getSdkLibraryName(), ps.getPkg().getSdkLibVersionMajor());
         if (libraryInfo == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 7242a56..6d31121 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -184,11 +184,11 @@
 
             if (pkg != null) {
                 SharedLibraryInfo libraryInfo = null;
-                if (pkg.getStaticSharedLibName() != null) {
-                    libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibName(),
+                if (pkg.getStaticSharedLibraryName() != null) {
+                    libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibraryName(),
                             pkg.getStaticSharedLibVersion());
-                } else if (pkg.getSdkLibName() != null) {
-                    libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibName(),
+                } else if (pkg.getSdkLibraryName() != null) {
+                    libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibraryName(),
                             pkg.getSdkLibVersionMajor());
                 }
 
@@ -544,7 +544,7 @@
             }
             outInfo.mRemovedPackage = ps.getPackageName();
             outInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
-            outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibName() != null;
+            outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
             outInfo.mRemovedAppId = ps.getAppId();
             outInfo.mRemovedUsers = userIds;
             outInfo.mBroadcastUsers = userIds;
@@ -573,7 +573,7 @@
         if (deleteCodeAndResources && (outInfo != null)) {
             outInfo.mArgs = new InstallArgs(
                     ps.getPathString(), getAppDexInstructionSets(
-                            ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()));
+                            ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy()));
             if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
         }
     }
@@ -895,8 +895,8 @@
             final String packageName = ps.getPkg().getPackageName();
             // Skip over if system app, static shared library or and SDK library.
             if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0
-                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())
-                    || !TextUtils.isEmpty(ps.getPkg().getSdkLibName())) {
+                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibraryName())
+                    || !TextUtils.isEmpty(ps.getPkg().getSdkLibraryName())) {
                 continue;
             }
             if (DEBUG_CLEAN_APKS) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 2b8a196..3f04264 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -58,7 +58,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 
@@ -385,6 +384,11 @@
         } else if (snapshot.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
             return false;
         }
+        var pkg = snapshot.getPackage(options.getPackageName());
+        if (pkg != null && pkg.isApex()) {
+            // skip APEX
+            return true;
+        }
 
         if (options.isDexoptOnlySecondaryDex()) {
             return mPm.getDexManager().dexoptSecondaryDex(options);
@@ -427,6 +431,10 @@
                 // Package could not be found. Report failure.
                 return PackageDexOptimizer.DEX_OPT_FAILED;
             }
+            if (p.isApex()) {
+                // APEX needs no dexopt
+                return PackageDexOptimizer.DEX_OPT_SKIPPED;
+            }
             mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
             mPm.mCompilerStats.maybeWriteAsync();
         }
@@ -461,8 +469,8 @@
         // others will see that the compiled code for the library is up to date.
         Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
         final String[] instructionSets = getAppDexInstructionSets(
-                AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
-                AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));
+                pkgSetting.getPrimaryCpuAbi(),
+                pkgSetting.getSecondaryCpuAbi());
         if (!deps.isEmpty()) {
             DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
                     options.getCompilationReason(), options.getCompilerFilter(),
@@ -498,6 +506,9 @@
         if (packageState == null || pkg == null) {
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
+        if (pkg.isApex()) {
+            throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
+        }
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 797d4c3..f6472a7 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -33,11 +33,9 @@
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -121,29 +119,6 @@
         mExecutorService = ParallelPackageParser.makeExecutorService();
     }
 
-    private List<File> getFrameworkResApkSplitFiles() {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
-        try {
-            final List<File> splits = new ArrayList<>();
-            final List<ApexManager.ActiveApexInfo> activeApexInfos =
-                    mPm.mApexManager.getActiveApexInfos();
-            for (int i = 0; i < activeApexInfos.size(); i++) {
-                ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
-                File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
-                if (splitsFolder.isDirectory()) {
-                    for (File file : splitsFolder.listFiles()) {
-                        if (ApkLiteParseUtils.isApkFile(file)) {
-                            splits.add(file);
-                        }
-                    }
-                }
-            }
-            return splits;
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
         scanPartitions.addAll(mSystemPartitions);
@@ -270,7 +245,7 @@
             long startTime) {
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                 SystemClock.uptimeMillis());
-        scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+        scanDirTracedLI(mPm.getAppInstallDir(), 0,
                 mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService);
 
         List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
@@ -338,15 +313,13 @@
             if (partition.getOverlayFolder() == null) {
                 continue;
             }
-            scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+            scanDirTracedLI(partition.getOverlayFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
 
-        List<File> frameworkSplits = getFrameworkResApkSplitFiles();
-        scanDirTracedLI(frameworkDir, frameworkSplits,
-                mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
-                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
+        scanDirTracedLI(frameworkDir,
+                mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
             throw new IllegalStateException(
@@ -356,12 +329,12 @@
         for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
             final ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.getPrivAppFolder() != null) {
-                scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+                scanDirTracedLI(partition.getPrivAppFolder(),
                         mSystemParseFlags,
                         mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
                         packageParser, executorService);
             }
-            scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+            scanDirTracedLI(partition.getAppFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
@@ -379,7 +352,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+    private void scanDirTracedLI(File scanDir,
             int parseFlags, int scanFlags,
             PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
@@ -388,7 +361,7 @@
                 // when scanning apk in apexes, we want to check the maxSdkVersion
                 parseFlags |= PARSE_APK_IN_APEX;
             }
-            mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,
                     scanFlags, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a25ee5e..30ecc1c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -168,6 +168,7 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 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.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedPermission;
@@ -351,7 +352,7 @@
         }
 
         if (reconciledPkg.mCollectedSharedLibraryInfos != null
-                || (oldPkgSetting != null && oldPkgSetting.getUsesLibraryInfos() != null)) {
+                || (oldPkgSetting != null && oldPkgSetting.getUsesLibraries() != null)) {
             // Reconcile if the new package or the old package uses shared libraries.
             // It is possible that the old package uses shared libraries but the new one doesn't.
             mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
@@ -442,7 +443,7 @@
         // Also need to kill any apps that are dependent on the library, except the case of
         // installation of new version static shared library.
         if (clientLibPkgs != null) {
-            if (pkg.getStaticSharedLibName() == null || isReplace) {
+            if (pkg.getStaticSharedLibraryName() == null || isReplace) {
                 for (int i = 0; i < clientLibPkgs.size(); i++) {
                     AndroidPackage clientPkg = clientLibPkgs.get(i);
                     mPm.killApplication(clientPkg.getPackageName(),
@@ -1141,7 +1142,7 @@
             if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) {
                 WatchedLongSparseArray<SharedLibraryInfo> libraryInfos =
                         mSharedLibraries.getSharedLibraryInfos(
-                                parsedPackage.getSdkLibName());
+                                parsedPackage.getSdkLibraryName());
                 if (libraryInfos != null && libraryInfos.size() > 0) {
                     // Any existing version would do.
                     SharedLibraryInfo libraryInfo = libraryInfos.valueAt(0);
@@ -1376,8 +1377,8 @@
 
                 // We moved the entire application as-is, so bring over the
                 // previously derived ABI information.
-                parsedPackage.setPrimaryCpuAbi(ps.getPrimaryCpuAbi())
-                        .setSecondaryCpuAbi(ps.getSecondaryCpuAbi());
+                parsedPackage.setPrimaryCpuAbi(ps.getPrimaryCpuAbiLegacy())
+                        .setSecondaryCpuAbi(ps.getSecondaryCpuAbiLegacy());
             }
 
         } else {
@@ -1578,7 +1579,7 @@
                 removedInfo.mInstallerPackageName =
                         ps.getInstallSource().installerPackageName;
                 removedInfo.mIsStaticSharedLib =
-                        parsedPackage.getStaticSharedLibName() != null;
+                        parsedPackage.getStaticSharedLibraryName() != null;
                 removedInfo.mIsUpdate = true;
                 removedInfo.mOrigUsers = installedUsers;
                 removedInfo.mInstallReasons = new SparseIntArray(installedUsers.length);
@@ -1931,10 +1932,8 @@
                         installRequest.getRemovedInfo().mArgs = new InstallArgs(
                                 oldPackage.getPath(),
                                 getAppDexInstructionSets(
-                                        AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
-                                                deletedPkgSetting),
-                                        AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
-                                                deletedPkgSetting)));
+                                        deletedPkgSetting.getPrimaryCpuAbi(),
+                                        deletedPkgSetting.getSecondaryCpuAbi()));
                     } else {
                         installRequest.getRemovedInfo().mArgs = null;
                     }
@@ -2059,9 +2058,9 @@
 
                 // Retrieve the overlays for shared libraries of the package.
                 if (!ps.getPkgState().getUsesLibraryInfos().isEmpty()) {
-                    for (SharedLibraryInfo sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
+                    for (SharedLibraryWrapper sharedLib : ps.getPkgState().getUsesLibraryInfos()) {
                         for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
-                            if (!sharedLib.isDynamic()) {
+                            if (sharedLib.getType() != SharedLibraryInfo.TYPE_DYNAMIC) {
                                 // TODO(146804378): Support overlaying static shared libraries
                                 continue;
                             }
@@ -2684,7 +2683,7 @@
             }
 
             // Send installed broadcasts if the package is not a static shared lib.
-            if (request.getPkg().getStaticSharedLibName() == null) {
+            if (request.getPkg().getStaticSharedLibraryName() == null) {
                 mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
 
                 // Send added for users that see the package for the first time
@@ -2813,7 +2812,7 @@
                 // No need to kill consumers if it's installation of new version static shared lib.
                 final Computer snapshot = mPm.snapshotComputer();
                 final boolean dontKillApp = !update
-                        && request.getPkg().getStaticSharedLibName() != null;
+                        && request.getPkg().getStaticSharedLibraryName() != null;
                 for (int i = 0; i < request.getLibraryConsumers().size(); i++) {
                     AndroidPackage pkg = request.getLibraryConsumers().get(i);
                     // send broadcast that all consumers of the static shared library have changed
@@ -3451,8 +3450,8 @@
 
             if (throwable == null) {
                 try {
-                    AndroidPackage pkg = addForInitLI(
-                            parseResult.parsedPackage, newParseFlags, newScanFlags, null);
+                    addForInitLI(parseResult.parsedPackage, newParseFlags, newScanFlags, null);
+                    AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal();
                     if (ai.isFactory && !ai.isActive) {
                         disableSystemPackageLPw(pkg);
                     }
@@ -3472,7 +3471,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+    public void installPackagesFromDir(File scanDir, int parseFlags,
             int scanFlags, PackageParser2 packageParser,
             ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
@@ -3486,7 +3485,7 @@
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
         ParallelPackageParser parallelPackageParser =
-                new ParallelPackageParser(packageParser, executorService, frameworkSplits);
+                new ParallelPackageParser(packageParser, executorService);
 
         // Submit files for parsing in parallel
         int fileCount = 0;
@@ -3943,8 +3942,8 @@
 
             mRemovePackageHelper.cleanUpResources(
                     new File(pkgSetting.getPathString()),
-                    getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
-                            pkgSetting.getSecondaryCpuAbi()));
+                    getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbiLegacy(),
+                            pkgSetting.getSecondaryCpuAbiLegacy()));
             synchronized (mPm.mLock) {
                 mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
             }
@@ -4028,7 +4027,7 @@
                                 + parsedPackage.getPath());
                 mRemovePackageHelper.cleanUpResources(new File(pkgSetting.getPathString()),
                         getAppDexInstructionSets(
-                                pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
+                                pkgSetting.getPrimaryCpuAbiLegacy(), pkgSetting.getSecondaryCpuAbiLegacy()));
             } else {
                 // The application on /system is older than the application on /data. Hide
                 // the application on /system and the version on /data will be scanned later
@@ -4297,7 +4296,7 @@
         long maxVersionCode = Long.MAX_VALUE;
 
         WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
-                mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibName());
+                mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibraryName());
         if (versionedLib != null) {
             final int versionCount = versionedLib.size();
             for (int i = 0; i < versionCount; i++) {
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 404285c..e5f7f71 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -131,7 +131,8 @@
             @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
                 && installerPackageName == null && initiatingPackageSignatures == null
-                && !isInitiatingPackageUninstalled) {
+                && !isInitiatingPackageUninstalled
+                && packageSource == PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED) {
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 71bd2d7..bedc12a 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -879,22 +879,22 @@
             });
         }
 
-        synchronized (mLock) {
-            if (packagesToDelete != null) {
-                final int packageCount = packagesToDelete.size();
-                for (int i = 0; i < packageCount; i++) {
-                    final String packageToDelete = packagesToDelete.get(i);
-                    if (mDeletePackageHelper.deletePackageX(packageToDelete,
-                            PackageManager.VERSION_CODE_HIGHEST,
-                            UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
-                            true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
-                        if (file.getUsableSpace() >= neededSpace) {
-                            return true;
-                        }
+        if (packagesToDelete != null) {
+            final int packageCount = packagesToDelete.size();
+            for (int i = 0; i < packageCount; i++) {
+                final String packageToDelete = packagesToDelete.get(i);
+                if (mDeletePackageHelper.deletePackageX(packageToDelete,
+                        PackageManager.VERSION_CODE_HIGHEST,
+                        UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
+                        true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
+                    if (file.getUsableSpace() >= neededSpace) {
+                        return true;
                     }
                 }
             }
+        }
 
+        synchronized (mLock) {
             // Prune uninstalled instant apps
             // TODO: Track last used time for uninstalled instant apps for better pruning
             for (int userId : mUserManager.getUserIds()) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 5507b44..56f2493 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -409,8 +409,8 @@
             }
 
             final String[] instructionSets = getAppDexInstructionSets(
-                    AndroidPackageUtils.getPrimaryCpuAbi(pkg, packageState),
-                    AndroidPackageUtils.getSecondaryCpuAbi(pkg, packageState));
+                    packageState.getPrimaryCpuAbi(),
+                    packageState.getSecondaryCpuAbi());
             final List<String> paths =
                     AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index 9bea599..d839b14 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -22,7 +22,6 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -119,11 +118,6 @@
             this.secondary = secondary;
         }
 
-        Abis(AndroidPackage pkg, PackageSetting pkgSetting)  {
-            this(AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
-                    AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting));
-        }
-
         public void applyTo(ParsedPackage pkg) {
             pkg.setPrimaryCpuAbi(primary)
                     .setSecondaryCpuAbi(secondary);
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 75f5f93..249de3c 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -517,12 +517,12 @@
                     ps.getPackageName())) {
                 continue;
             }
-            if (ps.getPrimaryCpuAbi() == null) {
+            if (ps.getPrimaryCpuAbiLegacy() == null) {
                 continue;
             }
 
             final String instructionSet =
-                    VMRuntime.getInstructionSet(ps.getPrimaryCpuAbi());
+                    VMRuntime.getInstructionSet(ps.getPrimaryCpuAbiLegacy());
             if (requiredInstructionSet != null && !requiredInstructionSet.equals(instructionSet)) {
                 // We have a mismatch between instruction sets (say arm vs arm64) warn about
                 // this but there's not much we can do.
@@ -548,7 +548,7 @@
             // scannedPackage did not require an ABI, in which case we have to adjust
             // scannedPackage to match the ABI of the set (which is the same as
             // requirer's ABI)
-            adjustedAbi = requirer.getPrimaryCpuAbi();
+            adjustedAbi = requirer.getPrimaryCpuAbiLegacy();
         } else {
             // requirer == null implies that we're updating all ABIs in the set to
             // match scannedPackage.
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 3fb4066..d25bca7 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -189,6 +189,11 @@
             return false;
         }
 
+        // We do not dexopt APEX packages.
+        if (pkg.isApex()) {
+            return false;
+        }
+
         // We do not dexopt unused packages.
         // It's possible for this to be called before app hibernation service is ready due to
         // an OTA dexopt. In this case, we ignore the hibernation check here. This is fine since
@@ -260,8 +265,8 @@
                 .getNonNativeUsesLibraryInfos();
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(
-                AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
-                AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting));
+                pkgSetting.getPrimaryCpuAbi(),
+                pkgSetting.getSecondaryCpuAbi());
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         final List<String> paths = AndroidPackageUtils.getAllCodePaths(pkg);
 
@@ -731,9 +736,8 @@
      */
     void dumpDexoptState(IndentingPrintWriter pw, AndroidPackage pkg,
             PackageStateInternal pkgSetting, PackageDexUsage.PackageUseInfo useInfo) {
-        final String[] instructionSets = getAppDexInstructionSets(
-                AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
-                AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting));
+        final String[] instructionSets = getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
+                pkgSetting.getSecondaryCpuAbi());
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
 
         final List<String> paths = AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 39cc37e..c9b48bf3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,11 +20,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * In-process API for server side PackageManager related infrastructure.
@@ -57,10 +63,10 @@
             FLAG_STORAGE_CE,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface StorageFlags {}
+    @interface StorageFlags {}
 
     /**
-     * Reconcile sdk data sub-directories for the given {@code packagName}.
+     * Reconcile sdk data sub-directories for the given {@code packageName}.
      *
      * Sub directories are created if they do not exist already. If there is an existing per-
      * sdk directory that is missing from {@code subDirNames}, then it is removed.
@@ -80,4 +86,100 @@
     void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
             @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
             @NonNull String seInfo, @StorageFlags int flags) throws IOException;
+
+    /**
+     * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and
+     * closed as soon as possible.
+     * <p/>
+     * All reachable types in the snapshot are read-only.
+     * <p/>
+     * The snapshot assumes the caller is acting on behalf of the system and will not filter any
+     * results.
+     *
+     * @hide
+     */
+    @NonNull
+    UnfilteredSnapshot withUnfilteredSnapshot();
+
+    /**
+     * {@link #withFilteredSnapshot(int, UserHandle)} that infers the UID and user from the
+     * caller through {@link Binder#getCallingUid()} and {@link Binder#getCallingUserHandle()}.
+     *
+     * @see #withFilteredSnapshot(int, UserHandle)
+     * @hide
+     */
+    @NonNull
+    FilteredSnapshot withFilteredSnapshot();
+
+    /**
+     * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and
+     * closed as soon as possible.
+     * <p/>
+     * All reachable types in the snapshot are read-only.
+     *
+     * @param callingUid The caller UID to filter results based on. This includes package visibility
+     *                   and permissions, including cross-user enforcement.
+     * @param user       The user to query as, should usually be the user that the caller was
+     *                   invoked from.
+     * @hide
+     */
+    @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user
+    @NonNull
+    FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
+
+    /**
+     * @hide
+     */
+    interface UnfilteredSnapshot extends AutoCloseable {
+
+        /**
+         * Allows re-use of this snapshot, but in a filtered context. This allows a caller to invoke
+         * itself as multiple other actual callers without having to re-take a snapshot.
+         * <p/>
+         * Note that closing the parent snapshot closes any filtered children generated from it.
+         *
+         * @return An isolated instance of {@link FilteredSnapshot} which can be closed without
+         * affecting this parent snapshot or any sibling snapshots.
+         */
+        @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user
+        @NonNull
+        FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user);
+
+        /**
+         * Returns a map of all {@link PackageState PackageStates} on the device.
+         *
+         * @return Mapping of package name to {@link PackageState}.
+         */
+        @NonNull
+        Map<String, PackageState> getPackageStates();
+
+        @Override
+        void close();
+    }
+
+    /**
+     * @hide
+     */
+    interface FilteredSnapshot extends AutoCloseable {
+
+        /**
+         * @return {@link PackageState} for the {@code packageName}, filtered if applicable.
+         */
+        @Nullable
+        PackageState getPackageState(@NonNull String packageName);
+
+        /**
+         * Iterates on all states. This should only be used when either the target package name
+         * is not known or the large majority of the states are expected to be used.
+         *
+         * This will cause app visibility filtering to be invoked on each state on the device,
+         * which can be expensive.
+         *
+         * @param consumer Block to accept each state as it becomes available post-filtering.
+         */
+        void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
+
+        @Override
+        void close();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 39a6eb7..d939ca6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -200,6 +200,7 @@
 import com.android.server.pm.dex.ArtUtils;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.local.PackageManagerLocalImpl;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
@@ -1071,10 +1072,27 @@
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     @NonNull
     public Computer snapshotComputer() {
-        if (Thread.holdsLock(mLock)) {
-            // If the current thread holds mLock then it may have modified state but not
-            // yet invalidated the snapshot.  Always give the thread the live computer.
-            return mLiveComputer;
+        return snapshotComputer(true /*allowLiveComputer*/);
+    }
+
+    /**
+     * This method should only ever be called from {@link PackageManagerLocal#snapshot()}.
+     *
+     * @param allowLiveComputer Whether to allow a live computer instance based on caller {@link
+     *                          #mLock} hold state. In certain cases, like for {@link
+     *                          PackageManagerLocal} API, it must be enforced that the caller gets
+     *                          a snapshot at time, and never the live variant.
+     * @deprecated Use {@link #snapshotComputer()}
+     */
+    @Deprecated
+    @NonNull
+    public Computer snapshotComputer(boolean allowLiveComputer) {
+        if (allowLiveComputer) {
+            if (Thread.holdsLock(mLock)) {
+                // If the current thread holds mLock then it may have modified state but not
+                // yet invalidated the snapshot.  Always give the thread the live computer.
+                return mLiveComputer;
+            }
         }
 
         var oldSnapshot = sSnapshot.get();
@@ -1531,7 +1549,8 @@
         ServiceManager.addService("package", iPackageManager);
         final PackageManagerNative pmn = new PackageManagerNative(m);
         ServiceManager.addService("package_native", pmn);
-        LocalManagerRegistry.addManager(PackageManagerLocal.class, m.new PackageManagerLocalImpl());
+        LocalManagerRegistry.addManager(PackageManagerLocal.class,
+                new PackageManagerLocalImpl(m));
         return Pair.create(m, iPackageManager);
     }
 
@@ -5489,19 +5508,19 @@
                 AndroidPackage pkg = packageState.getPkg();
                 if (pkg != null) {
                     // Cannot hide SDK libs as they are controlled by SDK manager.
-                    if (pkg.getSdkLibName() != null) {
+                    if (pkg.getSdkLibraryName() != null) {
                         Slog.w(TAG, "Cannot hide package: " + packageName
                                 + " providing SDK library: "
-                                + pkg.getSdkLibName());
+                                + pkg.getSdkLibraryName());
                         return false;
                     }
                     // Cannot hide static shared libs as they are considered
                     // a part of the using app (emulating static linking). Also
                     // static libs are installed always on internal storage.
-                    if (pkg.getStaticSharedLibName() != null) {
+                    if (pkg.getStaticSharedLibraryName() != null) {
                         Slog.w(TAG, "Cannot hide package: " + packageName
                                 + " providing static shared library: "
-                                + pkg.getStaticSharedLibName());
+                                + pkg.getStaticSharedLibraryName());
                         return false;
                     }
                 }
@@ -5544,17 +5563,17 @@
             if (packageState != null && packageState.getPkg() != null) {
                 AndroidPackage pkg = packageState.getPkg();
                 // Cannot block uninstall SDK libs as they are controlled by SDK manager.
-                if (pkg.getSdkLibName() != null) {
+                if (pkg.getSdkLibraryName() != null) {
                     Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName
-                            + " providing SDK library: " + pkg.getSdkLibName());
+                            + " providing SDK library: " + pkg.getSdkLibraryName());
                     return false;
                 }
                 // Cannot block uninstall of static shared libs as they are
                 // considered a part of the using app (emulating static linking).
                 // Also static libs are installed always on internal storage.
-                if (pkg.getStaticSharedLibName() != null) {
+                if (pkg.getStaticSharedLibraryName() != null) {
                     Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName
-                            + " providing static shared library: " + pkg.getStaticSharedLibName());
+                            + " providing static shared library: " + pkg.getStaticSharedLibraryName());
                     return false;
                 }
             }
@@ -6017,25 +6036,6 @@
         }
     }
 
-    private class PackageManagerLocalImpl implements PackageManagerLocal {
-        @Override
-        public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
-                @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
-                @NonNull String seInfo, int flags) throws IOException {
-            synchronized (mInstallLock) {
-                ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid,
-                        packageName, subDirNames, userId, appId, seInfo,
-                        flags);
-                args.previousAppId = previousAppId;
-                try {
-                    mInstaller.reconcileSdkData(args);
-                } catch (InstallerException e) {
-                    throw new IOException(e.getMessage());
-                }
-            }
-        }
-    }
-
     private class PackageManagerInternalImpl extends PackageManagerInternalBase {
 
         public PackageManagerInternalImpl() {
@@ -6539,7 +6539,7 @@
                             if (dependentState == null) {
                                 continue;
                             }
-                            if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+                            if (canSetOverlayPaths(dependentState.getUserStateOrDefault(userId)
                                     .getSharedLibraryOverlayPaths()
                                     .get(libName), newOverlayPaths)) {
                                 String dependentPackageName = dependent.getPackageName();
@@ -6563,7 +6563,10 @@
                     }
                 }
 
-                outUpdatedPackageNames.add(targetPackageName);
+                if (canSetOverlayPaths(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+                        newOverlayPaths)) {
+                    outUpdatedPackageNames.add(targetPackageName);
+                }
             }
 
             commitPackageStateMutation(null, mutator -> {
@@ -6614,6 +6617,17 @@
         invalidatePackageInfoCache();
     }
 
+    private boolean canSetOverlayPaths(OverlayPaths origPaths, OverlayPaths newPaths) {
+        if (Objects.equals(origPaths, newPaths)) {
+            return false;
+        }
+        if ((origPaths == null && newPaths.isEmpty())
+                || (newPaths == null && origPaths.isEmpty())) {
+            return false;
+        }
+        return true;
+    }
+
     private void maybeUpdateSystemOverlays(String targetPackageName, OverlayPaths newOverlayPaths) {
         if (!mResolverReplaced) {
             if (targetPackageName.equals("android")) {
@@ -7282,4 +7296,20 @@
             mSettings.addInstallerPackageNames(installSource);
         }
     }
+
+    public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
+            @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
+            @NonNull String seInfo, int flags) throws IOException {
+        synchronized (mInstallLock) {
+            ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid,
+                    packageName, subDirNames, userId, appId, seInfo,
+                    flags);
+            args.previousAppId = previousAppId;
+            try {
+                mInstaller.reconcileSdkData(args);
+            } catch (InstallerException e) {
+                throw new IOException(e.getMessage());
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7faebb5..76858d9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -318,6 +318,8 @@
                     return runRemoveUser();
                 case "set-user-restriction":
                     return runSetUserRestriction();
+                case "supports-multiple-users":
+                    return runSupportsMultipleUsers();
                 case "get-max-users":
                     return runGetMaxUsers();
                 case "get-max-running-users":
@@ -3053,6 +3055,12 @@
         return 0;
     }
 
+    public int runSupportsMultipleUsers() {
+        getOutPrintWriter().println("Is multiuser supported: "
+                + UserManager.supportsMultipleUsers());
+        return 0;
+    }
+
     public int runGetMaxUsers() {
         getOutPrintWriter().println("Maximum supported users: "
                 + UserManager.getMaxSupportedUsers());
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 4764a5c..8d6abe0 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -33,6 +33,7 @@
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
 import android.service.pm.PackageProto;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -42,15 +43,18 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.server.pm.parsing.pkg.AndroidPackageInternal;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 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.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
+import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SharedLibrary;
+import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.pm.pkg.SuspendParams;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.WatchedArraySet;
@@ -1203,8 +1207,14 @@
 
     @NonNull
     @Override
-    public List<SharedLibraryInfo> getUsesLibraryInfos() {
-        return pkgState.getUsesLibraryInfos();
+    public List<SharedLibrary> getUsesLibraries() {
+        return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
+    }
+
+    @NonNull
+    public PackageSetting addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
+        pkgState.addUsesLibraryInfo(new SharedLibraryWrapper(value));
+        return this;
     }
 
     @NonNull
@@ -1213,6 +1223,12 @@
         return pkgState.getUsesLibraryFiles();
     }
 
+    @NonNull
+    public PackageSetting addUsesLibraryFile(String value) {
+        pkgState.addUsesLibraryFile(value);
+        return this;
+    }
+
     @Override
     public boolean isHiddenUntilInstalled() {
         return pkgState.isHiddenUntilInstalled();
@@ -1314,6 +1330,41 @@
         return this;
     }
 
+    @NonNull
+    @Override
+    public PackageUserState getStateForUser(@NonNull UserHandle user) {
+        PackageUserState userState = getUserStates().get(user.getIdentifier());
+        return userState == null ? PackageUserState.DEFAULT : userState;
+    }
+
+    @Nullable
+    public String getPrimaryCpuAbi() {
+        if (TextUtils.isEmpty(mPrimaryCpuAbi) && pkg != null) {
+            return AndroidPackageUtils.getRawPrimaryCpuAbi(pkg);
+        }
+
+        return mPrimaryCpuAbi;
+    }
+
+    @Nullable
+    public String getSecondaryCpuAbi() {
+        if (TextUtils.isEmpty(mSecondaryCpuAbi) && pkg != null) {
+            return AndroidPackageUtils.getRawSecondaryCpuAbi(pkg);
+        }
+
+        return mSecondaryCpuAbi;
+    }
+
+    @Nullable
+    public String getPrimaryCpuAbiLegacy() {
+        return mPrimaryCpuAbi;
+    }
+
+    @Nullable
+    public String getSecondaryCpuAbiLegacy() {
+        return mSecondaryCpuAbi;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1391,16 +1442,6 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable String getPrimaryCpuAbi() {
-        return mPrimaryCpuAbi;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getSecondaryCpuAbi() {
-        return mSecondaryCpuAbi;
-    }
-
-    @DataClass.Generated.Member
     public @Nullable String getCpuAbiOverride() {
         return mCpuAbiOverride;
     }
@@ -1475,10 +1516,10 @@
     }
 
     @DataClass.Generated(
-            time = 1659546705292L,
+            time = 1662666062860L,
             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 @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 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 setInstallerPackageName(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.parsing.pkg.AndroidPackageInternal)\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 isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\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<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\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  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 @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>)\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 @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 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 setInstallerPackageName(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 isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\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> getUsesLibraries()\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  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 @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)\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/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 45030bf..5625884 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,7 +27,6 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
-import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
@@ -55,17 +54,9 @@
 
     private final ExecutorService mExecutorService;
 
-    private final List<File> mFrameworkSplits;
-
     ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
-        this(packageParser, executorService, /* frameworkSplits= */ null);
-    }
-
-    ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
-            List<File> frameworkSplits) {
         mPackageParser = packageParser;
         mExecutorService = executorService;
-        mFrameworkSplits = frameworkSplits;
     }
 
     static class ParseResult {
@@ -134,6 +125,6 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
+        return mPackageParser.parsePackage(scanFile, parseFlags, true);
     }
 }
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 55d4b36..bbc4fde 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -215,21 +215,21 @@
         r = null;
 
         // Any package can hold SDK or static shared libraries.
-        if (pkg.getSdkLibName() != null) {
+        if (pkg.getSdkLibraryName() != null) {
             if (mSharedLibraries.removeSharedLibrary(
-                    pkg.getSdkLibName(), pkg.getSdkLibVersionMajor())) {
+                    pkg.getSdkLibraryName(), pkg.getSdkLibVersionMajor())) {
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
                     } else {
                         r.append(' ');
                     }
-                    r.append(pkg.getSdkLibName());
+                    r.append(pkg.getSdkLibraryName());
                 }
             }
         }
-        if (pkg.getStaticSharedLibName() != null) {
-            if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibName(),
+        if (pkg.getStaticSharedLibraryName() != null) {
+            if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibraryName(),
                     pkg.getStaticSharedLibVersion())) {
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
@@ -237,7 +237,7 @@
                     } else {
                         r.append(' ');
                     }
-                    r.append(pkg.getStaticSharedLibName());
+                    r.append(pkg.getStaticSharedLibraryName());
                 }
             }
         }
@@ -271,7 +271,7 @@
             outInfo.mRemovedPackage = packageName;
             outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
             outInfo.mIsStaticSharedLib = deletedPkg != null
-                    && deletedPkg.getStaticSharedLibName() != null;
+                    && deletedPkg.getStaticSharedLibraryName() != null;
             outInfo.populateUsers(deletedPs.queryInstalledUsers(
                     mUserManagerInternal.getUserIds(), true), deletedPs);
             outInfo.mIsExternal = deletedPs.isExternalStorage();
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 9bd8e12..bd3c7dd 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -157,8 +157,8 @@
                 if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
                     needToDeriveAbi = true;
                 } else {
-                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
-                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
+                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbiLegacy();
+                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbiLegacy();
                 }
             } else {
                 // Re-scanning a system package after uninstalling updates; need to derive ABI
@@ -229,8 +229,8 @@
             // to null here, only to reset them at a later point.
             Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, oldSharedUserSetting,
                     sharedUserSetting, destCodeFile, parsedPackage.getNativeLibraryDir(),
-                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
-                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
+                    pkgSetting.getPrimaryCpuAbi(),
+                    pkgSetting.getSecondaryCpuAbi(),
                     PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
                     PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                     UserManagerService.getInstance(),
@@ -327,8 +327,8 @@
                 // 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.getPrimaryCpuAbi())
-                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
+                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
+                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
             }
 
             // Set native library paths again. For moves, the path will be updated based on the
@@ -378,8 +378,8 @@
 
         if (DEBUG_ABI_SELECTION) {
             Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
-                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
-                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
+                    + " primary=" + pkgSetting.getPrimaryCpuAbiLegacy()
+                    + " secondary=" + pkgSetting.getSecondaryCpuAbiLegacy()
                     + " abiOverride=" + pkgSetting.getCpuAbiOverride());
         }
 
@@ -445,11 +445,11 @@
         }
 
         SharedLibraryInfo sdkLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
+        if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
             sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
         }
         SharedLibraryInfo staticSharedLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
+        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibraryName())) {
             staticSharedLibraryInfo =
                     AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
         }
@@ -901,7 +901,7 @@
             PackageSetting ps = sharedUserPackageSettings.valueAt(i);
             if (scannedPackage == null
                     || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
-                if (ps.getPrimaryCpuAbi() != null) {
+                if (ps.getPrimaryCpuAbiLegacy() != null) {
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9037f04..f2a7651 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -837,8 +837,8 @@
         }
         p.getPkgState().setUpdatedSystemApp(false);
         PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
-                p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbi(),
-                p.getSecondaryCpuAbi(), p.getCpuAbiOverride(),
+                p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbiLegacy(),
+                p.getSecondaryCpuAbiLegacy(), p.getCpuAbiOverride(),
                 p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
                 p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
                 p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
@@ -2796,11 +2796,11 @@
         if (pkg.getLegacyNativeLibraryPath() != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
         }
-        if (pkg.getPrimaryCpuAbi() != null) {
-           serializer.attribute(null, "primaryCpuAbi", pkg.getPrimaryCpuAbi());
+        if (pkg.getPrimaryCpuAbiLegacy() != null) {
+           serializer.attribute(null, "primaryCpuAbi", pkg.getPrimaryCpuAbiLegacy());
         }
-        if (pkg.getSecondaryCpuAbi() != null) {
-            serializer.attribute(null, "secondaryCpuAbi", pkg.getSecondaryCpuAbi());
+        if (pkg.getSecondaryCpuAbiLegacy() != null) {
+            serializer.attribute(null, "secondaryCpuAbi", pkg.getSecondaryCpuAbiLegacy());
         }
         if (pkg.getCpuAbiOverride() != null) {
             serializer.attribute(null, "cpuAbiOverride", pkg.getCpuAbiOverride());
@@ -2834,11 +2834,11 @@
         if (pkg.getLegacyNativeLibraryPath() != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
         }
-        if (pkg.getPrimaryCpuAbi() != null) {
-            serializer.attribute(null, "primaryCpuAbi", pkg.getPrimaryCpuAbi());
+        if (pkg.getPrimaryCpuAbiLegacy() != null) {
+            serializer.attribute(null, "primaryCpuAbi", pkg.getPrimaryCpuAbiLegacy());
         }
-        if (pkg.getSecondaryCpuAbi() != null) {
-            serializer.attribute(null, "secondaryCpuAbi", pkg.getSecondaryCpuAbi());
+        if (pkg.getSecondaryCpuAbiLegacy() != null) {
+            serializer.attribute(null, "secondaryCpuAbi", pkg.getSecondaryCpuAbiLegacy());
         }
         if (pkg.getCpuAbiOverride() != null) {
             serializer.attribute(null, "cpuAbiOverride", pkg.getCpuAbiOverride());
@@ -4561,8 +4561,8 @@
             pw.print(prefix); pw.print("  extractNativeLibs=");
             pw.println((ps.getFlags() & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0
                     ? "true" : "false");
-            pw.print(prefix); pw.print("  primaryCpuAbi="); pw.println(ps.getPrimaryCpuAbi());
-            pw.print(prefix); pw.print("  secondaryCpuAbi="); pw.println(ps.getSecondaryCpuAbi());
+            pw.print(prefix); pw.print("  primaryCpuAbi="); pw.println(ps.getPrimaryCpuAbiLegacy());
+            pw.print(prefix); pw.print("  secondaryCpuAbi="); pw.println(ps.getSecondaryCpuAbiLegacy());
             pw.print(prefix); pw.print("  cpuAbiOverride="); pw.println(ps.getCpuAbiOverride());
         }
         pw.print(prefix); pw.print("  versionCode="); pw.print(ps.getVersionCode());
@@ -4667,17 +4667,17 @@
                             pw.println(libraryNames.get(i));
                 }
             }
-            if (pkg.getStaticSharedLibName() != null) {
+            if (pkg.getStaticSharedLibraryName() != null) {
                 pw.print(prefix); pw.println("  static library:");
                 pw.print(prefix); pw.print("    ");
-                pw.print("name:"); pw.print(pkg.getStaticSharedLibName());
+                pw.print("name:"); pw.print(pkg.getStaticSharedLibraryName());
                 pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
             }
 
-            if (pkg.getSdkLibName() != null) {
+            if (pkg.getSdkLibraryName() != null) {
                 pw.print(prefix); pw.println("  SDK library:");
                 pw.print(prefix); pw.print("    ");
-                pw.print("name:"); pw.print(pkg.getSdkLibName());
+                pw.print("name:"); pw.print(pkg.getSdkLibraryName());
                 pw.print(" versionMajor:"); pw.println(pkg.getSdkLibVersionMajor());
             }
 
@@ -5503,6 +5503,7 @@
                                     + "set before trying to update the fingerprint.");
                 }
                 mFingerprints.put(userId, mExtendedFingerprint);
+                mPermissionUpgradeNeeded.put(userId, false);
                 writeStateForUserAsync(userId);
             }
         }
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 5905741..094e748 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -400,7 +400,7 @@
     @Nullable
     private SharedLibraryInfo getLatestStaticSharedLibraVersionLPr(@NonNull AndroidPackage pkg) {
         WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
-                pkg.getStaticSharedLibName());
+                pkg.getStaticSharedLibraryName());
         if (versionedLib == null) {
             return null;
         }
@@ -457,15 +457,15 @@
         // - Package manager is in a state where package isn't scanned yet. This will
         //   get called again after scanning to fix the dependencies.
         if (AndroidPackageUtils.isLibrary(pkg)) {
-            if (pkg.getSdkLibName() != null) {
+            if (pkg.getSdkLibraryName() != null) {
                 SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
-                        pkg.getSdkLibName(), pkg.getSdkLibVersionMajor());
+                        pkg.getSdkLibraryName(), pkg.getSdkLibVersionMajor());
                 if (definedLibrary != null) {
                     action.accept(definedLibrary, libInfo);
                 }
-            } else if (pkg.getStaticSharedLibName() != null) {
+            } else if (pkg.getStaticSharedLibraryName() != null) {
                 SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
-                        pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion());
+                        pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibVersion());
                 if (definedLibrary != null) {
                     action.accept(definedLibrary, libInfo);
                 }
@@ -691,9 +691,9 @@
                         && !hasString(pkg.getUsesLibraries(), changingPkg.getLibraryNames())
                         && !hasString(pkg.getUsesOptionalLibraries(), changingPkg.getLibraryNames())
                         && !ArrayUtils.contains(pkg.getUsesStaticLibraries(),
-                        changingPkg.getStaticSharedLibName())
+                        changingPkg.getStaticSharedLibraryName())
                         && !ArrayUtils.contains(pkg.getUsesSdkLibraries(),
-                        changingPkg.getSdkLibName())) {
+                        changingPkg.getSdkLibraryName())) {
                     continue;
                 }
                 if (resultList == null) {
diff --git a/services/core/java/com/android/server/pm/SharedLibraryUtils.java b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
index 274870d..2c28791 100644
--- a/services/core/java/com/android/server/pm/SharedLibraryUtils.java
+++ b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
@@ -20,6 +20,7 @@
 import android.content.pm.SharedLibraryInfo;
 
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.utils.WatchedLongSparseArray;
 
 import java.util.ArrayList;
@@ -79,8 +80,8 @@
         if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) {
             ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
             Set<String> collectedNames = new HashSet<>();
-            for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
-                findSharedLibrariesRecursive(info, retValue, collectedNames);
+            for (SharedLibraryWrapper info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
+                findSharedLibrariesRecursive(info.getInfo(), retValue, collectedNames);
             }
             return retValue;
         } else {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0c601bf..890c891 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1962,10 +1962,15 @@
 
                             continue;
                         case TAG_SHORTCUT:
-                            final ShortcutInfo si = parseShortcut(parser, packageName,
-                                    shortcutUser.getUserId(), fromBackup);
-                            // Don't use addShortcut(), we don't need to save the icon.
-                            ret.mShortcuts.put(si.getId(), si);
+                            try {
+                                final ShortcutInfo si = parseShortcut(parser, packageName,
+                                        shortcutUser.getUserId(), fromBackup);
+                                // Don't use addShortcut(), we don't need to save the icon.
+                                ret.mShortcuts.put(si.getId(), si);
+                            } catch (Exception e) {
+                                // b/246540168 malformed shortcuts should be ignored
+                                Slog.e(TAG, "Failed parsing shortcut.", e);
+                            }
                             continue;
                         case TAG_SHARE_TARGET:
                             ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index df7e375..51bb412 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -108,12 +108,12 @@
         final SuspendParams newSuspendParams =
                 new SuspendParams(dialogInfo, appExtras, launcherExtras);
 
-        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
-        final IntArray changedUids = new IntArray(packageNames.length);
-        final IntArray modifiedUids = new IntArray(packageNames.length);
         final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
 
-        ArraySet<String> modifiedPackages = new ArraySet<>();
+        final List<String> notifyPackagesList = new ArrayList<>(packageNames.length);
+        final IntArray notifyUids = new IntArray(packageNames.length);
+        final ArraySet<String> changedPackagesList = new ArraySet<>(packageNames.length);
+        final IntArray changedUids = new IntArray(packageNames.length);
 
         final boolean[] canSuspend = suspended
                 ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid) : null;
@@ -140,21 +140,17 @@
 
             final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
                     packageState.getUserStateOrDefault(userId).getSuspendParams();
-            if (suspended) {
-                if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) {
-                    final SuspendParams suspendParams = suspendParamsMap.get(packageName);
-                    // Skip if there's no changes
-                    if (suspendParams != null
-                            && Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
-                            && Objects.equals(suspendParams.getAppExtras(), appExtras)
-                            && Objects.equals(suspendParams.getLauncherExtras(),
-                            launcherExtras)) {
-                        // Carried over API behavior, must notify change even if no change
-                        changedPackagesList.add(packageName);
-                        changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
-                        continue;
-                    }
-                }
+
+            SuspendParams oldSuspendParams = suspendParamsMap == null
+                    ? null : suspendParamsMap.get(packageName);
+            boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
+
+            if (suspended && !changed) {
+                // Carried over API behavior, must notify change even if no change
+                notifyPackagesList.add(packageName);
+                notifyUids.add(
+                        UserHandle.getUid(userId, packageState.getAppId()));
+                continue;
             }
 
             // If only the callingPackage is suspending this package,
@@ -163,18 +159,21 @@
                     && CollectionUtils.size(suspendParamsMap) == 1
                     && suspendParamsMap.containsKey(callingPackage);
             if (suspended || packageUnsuspended) {
+                // Always notify of a suspend call + notify when fully unsuspended
+                notifyPackagesList.add(packageName);
+                notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+            }
+
+            if (changed) {
                 changedPackagesList.add(packageName);
                 changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
             }
-
-            modifiedPackages.add(packageName);
-            modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
         }
 
         mPm.commitPackageStateMutation(null, mutator -> {
-            final int size = modifiedPackages.size();
+            final int size = changedPackagesList.size();
             for (int index = 0; index < size; index++) {
-                final String packageName  = modifiedPackages.valueAt(index);
+                final String packageName  = changedPackagesList.valueAt(index);
                 final PackageUserStateWrite userState = mutator.forPackage(packageName)
                         .userState(userId);
                 if (suspended) {
@@ -185,19 +184,20 @@
             }
         });
 
-        if (!changedPackagesList.isEmpty()) {
-            final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+        if (!notifyPackagesList.isEmpty()) {
+            final String[] changedPackages =
+                    notifyPackagesList.toArray(new String[0]);
             sendPackagesSuspendedForUser(
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    changedPackages, changedUids.toArray(), userId);
+                    changedPackages, notifyUids.toArray(), userId);
             sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
             mPm.scheduleWritePackageRestrictions(userId);
         }
         // Send the suspension changed broadcast to ensure suspension state is not stale.
-        if (!modifiedPackages.isEmpty()) {
+        if (!changedPackagesList.isEmpty()) {
             sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
-                    modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
+                    changedPackagesList.toArray(new String[0]), changedUids.toArray(), userId);
         }
         return unmodifiablePackages.toArray(new String[0]);
     }
@@ -548,7 +548,7 @@
                     if (pkg.isSdkLibrary()) {
                         Slog.w(TAG, "Cannot suspend package: " + packageName
                                 + " providing SDK library: "
-                                + pkg.getSdkLibName());
+                                + pkg.getSdkLibraryName());
                         continue;
                     }
                     // Cannot suspend static shared libs as they are considered
@@ -557,7 +557,7 @@
                     if (pkg.isStaticSharedLibrary()) {
                         Slog.w(TAG, "Cannot suspend package: " + packageName
                                 + " providing static shared library: "
-                                + pkg.getStaticSharedLibName());
+                                + pkg.getStaticSharedLibraryName());
                         continue;
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index b620249..b977025 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -46,15 +46,6 @@
     public @interface OwnerType {
     }
 
-    // TODO(b/245963156): move to Display.java (and @hide) if we decide to support profiles on MUMD
-    /**
-     * Used only when starting a profile (on systems that
-     * {@link android.os.UserManager#isUsersOnSecondaryDisplaysSupported() support users running on
-     * secondary displays}), to indicate the profile should be started in the same display as its
-     * parent user.
-     */
-    public static final int PARENT_DISPLAY = -2;
-
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c77459d..0a89d13 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -88,8 +88,6 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
 import android.provider.Settings;
-import android.security.GateKeeper;
-import android.service.gatekeeper.IGateKeeperService;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
@@ -4664,6 +4662,10 @@
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
             t.traceEnd();
 
+            t.traceBegin("LSS.createNewUser");
+            mLockPatternUtils.createNewUser(userId, userInfo.serialNumber);
+            t.traceEnd();
+
             final Set<String> userTypeInstallablePackages =
                     mSystemPackageInstaller.getInstallablePackagesForUserType(userType);
             t.traceBegin("PM.createNewUser");
@@ -5199,8 +5201,9 @@
     }
 
     /**
-     * Removes a user and all data directories created for that user. This method should be called
-     * after the user's processes have been terminated.
+     * Removes a user and its profiles along with all data directories created for that user
+     * and its profile.
+     * This method should be called after the user's processes have been terminated.
      * @param userId the user's id
      */
     @Override
@@ -5213,13 +5216,52 @@
             Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
             return false;
         }
+        return removeUserWithProfilesUnchecked(userId);
+    }
+
+    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;
+        }
+
+        if (!userInfo.isProfile()) {
+            int[] profileIds = getProfileIds(userId, false);
+            for (int profileId : profileIds) {
+                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);
+                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
+                    // documentation, an ephemeral user should only be marked as disabled
+                    // when its removal is in progress.
+                    Slog.i(LOG_TAG, "Unable to immediately remove profile " + profileId
+                            + "associated with user " + userId + ". User is set as ephemeral "
+                            + "and will be removed on user switch or reboot.");
+                    synchronized (mPackagesLock) {
+                        UserData profileData = getUserDataNoChecks(userId);
+                        profileData.info.flags |= UserInfo.FLAG_EPHEMERAL;
+
+                        writeUserLP(profileData);
+                    }
+                }
+            }
+        }
+
         return removeUserUnchecked(userId);
     }
 
     @Override
     public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         checkCreateUsersPermission("Only the system can remove users");
-        return removeUserUnchecked(userId);
+        return removeUserWithProfilesUnchecked(userId);
     }
 
     /**
@@ -5255,13 +5297,13 @@
                     }
 
                     if (userData == null) {
-                        Slog.e(LOG_TAG, String.format(
+                        Slog.e(LOG_TAG, TextUtils.formatSimple(
                                 "Cannot remove user %d, invalid user id provided.", userId));
                         return false;
                     }
 
                     if (mRemovingUserIds.get(userId)) {
-                        Slog.e(LOG_TAG, String.format(
+                        Slog.e(LOG_TAG, TextUtils.formatSimple(
                                 "User %d is already scheduled for removal.", userId));
                         return false;
                     }
@@ -5372,7 +5414,7 @@
                 final int currentUser = getCurrentUserId();
                 if (currentUser != userId) {
                     // Attempt to remove the user. This will fail if the user is the current user
-                    if (removeUserUnchecked(userId)) {
+                    if (removeUserWithProfilesUnchecked(userId)) {
                         return UserManager.REMOVE_RESULT_REMOVED;
                     }
                 }
@@ -5460,15 +5502,8 @@
             Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
         }
 
-        // Cleanup gatekeeper secure user id
-        try {
-            final IGateKeeperService gk = GateKeeper.getService();
-            if (gk != null) {
-                gk.clearSecureUserId(userId);
-            }
-        } catch (Exception ex) {
-            Slog.w(LOG_TAG, "unable to clear GK secure user id");
-        }
+        // Cleanup lock settings
+        mLockPatternUtils.removeUser(userId);
 
         // Cleanup package manager settings
         mPm.cleanUpUser(this, userId);
@@ -6618,7 +6653,7 @@
 
         @Override
         public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
-            return removeUserUnchecked(userId);
+            return removeUserWithProfilesUnchecked(userId);
         }
 
         @Override
@@ -6822,13 +6857,25 @@
                 Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
             }
 
+            // NOTE: Using Boolean instead of boolean as it will be re-used below
+            Boolean isProfile = null;
             if (displayId == Display.DEFAULT_DISPLAY) {
-                // Don't need to do anything because methods (such as isUserVisible()) already know
-                // that the current user (and their profiles) is assigned to the default display.
-                if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "ignoring on default display");
+                if (mUsersOnSecondaryDisplaysEnabled) {
+                    // Profiles are only supported in the default display, but it cannot return yet
+                    // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
+                    // (this is done indirectly below when it checks that the profile parent is the
+                    // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+                    isProfile = isProfileUnchecked(userId);
                 }
-                return;
+                if (isProfile == null || !isProfile) {
+                    // Don't need to do anything because methods (such as isUserVisible()) already
+                    // know that the current user (and their profiles) is assigned to the default
+                    // display.
+                    if (DBG_MUMD) {
+                        Slogf.d(LOG_TAG, "ignoring on default display");
+                    }
+                    return;
+                }
             }
 
             if (!mUsersOnSecondaryDisplaysEnabled) {
@@ -6846,18 +6893,21 @@
             Preconditions.checkArgument(userId != currentUserId,
                     "Cannot assign current user (%d) to other displays", currentUserId);
 
+            if (isProfile == null) {
+                isProfile = isProfileUnchecked(userId);
+            }
             synchronized (mUsersOnSecondaryDisplays) {
-                if (isProfileUnchecked(userId)) {
-                    // Profile can only start in the same display as parent
-                    Preconditions.checkArgument(displayId == UserManagerInternal.PARENT_DISPLAY,
-                            "Profile user can only be started in the same display as parent");
+                if (isProfile) {
+                    // Profile can only start in the same display as parent. And for simplicity,
+                    // that display must be the DEFAULT_DISPLAY.
+                    Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+                            "Profile user can only be started in the default display");
                     int parentUserId = getProfileParentId(userId);
-                    int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+                    Preconditions.checkArgument(parentUserId == currentUserId,
+                            "Only profile of current user can be assigned to a display");
                     if (DBG_MUMD) {
-                        Slogf.d(LOG_TAG, "Adding profile user %d -> display %d", userId,
-                                parentDisplayId);
+                        Slogf.d(LOG_TAG, "Ignoring profile user %d on default display", userId);
                     }
-                    mUsersOnSecondaryDisplays.put(userId, parentDisplayId);
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/pm/dex/ArtUtils.java b/services/core/java/com/android/server/pm/dex/ArtUtils.java
index 77aefc5c..160add6 100644
--- a/services/core/java/com/android/server/pm/dex/ArtUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtUtils.java
@@ -42,9 +42,8 @@
             AndroidPackage pkg, PackageStateInternal pkgSetting) {
         return new ArtPackageInfo(
                 pkg.getPackageName(),
-                Arrays.asList(getAppDexInstructionSets(
-                        AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
-                        AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting))),
+                Arrays.asList(getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
+                        pkgSetting.getSecondaryCpuAbi())),
                 AndroidPackageUtils.getAllCodePaths(pkg),
                 getOatDir(pkg, pkgSetting));
     }
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
new file mode 100644
index 0000000..00c8f84
--- /dev/null
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.local;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.server.pm.Computer;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/** @hide */
+public class PackageManagerLocalImpl implements PackageManagerLocal {
+
+    private final PackageManagerService mService;
+
+    public PackageManagerLocalImpl(PackageManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
+            @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
+            @NonNull String seInfo, int flags) throws IOException {
+        mService.reconcileSdkData(volumeUuid, packageName, subDirNames, userId, appId,
+                previousAppId, seInfo, flags);
+    }
+
+    @NonNull
+    @Override
+    public UnfilteredSnapshotImpl withUnfilteredSnapshot() {
+        return new UnfilteredSnapshotImpl(mService.snapshotComputer(false /*allowLiveComputer*/));
+    }
+
+    @NonNull
+    @Override
+    public FilteredSnapshotImpl withFilteredSnapshot() {
+        return withFilteredSnapshot(Binder.getCallingUid(), Binder.getCallingUserHandle());
+    }
+
+    @NonNull
+    @Override
+    public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) {
+        return new FilteredSnapshotImpl(callingUid, user,
+                mService.snapshotComputer(false /*allowLiveComputer*/), null);
+    }
+
+    private abstract static class BaseSnapshotImpl implements AutoCloseable {
+
+        private boolean mClosed;
+
+        @NonNull
+        protected Computer mSnapshot;
+
+        private BaseSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
+            mSnapshot = (Computer) snapshot;
+        }
+
+        @Override
+        public void close() {
+            mClosed = true;
+            mSnapshot = null;
+            // TODO: Recycle snapshots?
+        }
+
+        @CallSuper
+        protected void checkClosed() {
+            if (mClosed) {
+                throw new IllegalStateException("Snapshot already closed");
+            }
+        }
+    }
+
+    private static class UnfilteredSnapshotImpl extends BaseSnapshotImpl implements
+            UnfilteredSnapshot {
+
+        private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
+            super(snapshot);
+        }
+
+        @Override
+        public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
+            return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this);
+        }
+
+        @SuppressWarnings("RedundantSuppression")
+        @NonNull
+        @Override
+        public Map<String, PackageState> getPackageStates() {
+            checkClosed();
+
+            //noinspection unchecked, RedundantCast
+            return (Map<String, PackageState>) (Map<?, ?>) mSnapshot.getPackageStates();
+        }
+    }
+
+    private static class FilteredSnapshotImpl extends BaseSnapshotImpl implements
+            FilteredSnapshot {
+
+        private final int mCallingUid;
+
+        @UserIdInt
+        private final int mUserId;
+
+        @Nullable
+        private ArrayList<PackageState> mFilteredPackageStates;
+
+        @Nullable
+        private final UnfilteredSnapshotImpl mParentSnapshot;
+
+        private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
+                @NonNull PackageDataSnapshot snapshot,
+                @Nullable UnfilteredSnapshotImpl parentSnapshot) {
+            super(snapshot);
+            mCallingUid = callingUid;
+            mUserId = user.getIdentifier();
+            mParentSnapshot = parentSnapshot;
+        }
+
+        @Override
+        protected void checkClosed() {
+            if (mParentSnapshot != null) {
+                mParentSnapshot.checkClosed();
+            }
+
+            super.checkClosed();
+        }
+
+        @Nullable
+        @Override
+        public PackageState getPackageState(@NonNull String packageName) {
+            checkClosed();
+            return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
+        }
+
+        @Override
+        public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+            checkClosed();
+
+            if (mFilteredPackageStates == null) {
+                var packageStates = mSnapshot.getPackageStates();
+                var filteredPackageStates = new ArrayList<PackageState>();
+                for (int index = 0, size = packageStates.size(); index < size; index++) {
+                    var packageState = packageStates.valueAt(index);
+                    if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
+                        filteredPackageStates.add(packageState);
+                    }
+                }
+                mFilteredPackageStates = filteredPackageStates;
+            }
+
+            for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
+                var packageState = mFilteredPackageStates.get(index);
+                consumer.accept(packageState);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 1084145..be3a4da 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -81,6 +81,7 @@
 import libcore.util.EmptyArray;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -472,7 +473,11 @@
             PackageStateUnserialized pkgState = pkgSetting.getTransientState();
             info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
             List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
-            List<SharedLibraryInfo> usesLibraryInfos = pkgState.getUsesLibraryInfos();
+            var usesLibraries = pkgState.getUsesLibraryInfos();
+            var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+            for (int index = 0; index < usesLibraries.size(); index++) {
+                usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+            }
             info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
                     ? null : usesLibraryFiles.toArray(new String[0]);
             info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
@@ -482,8 +487,10 @@
         }
 
         info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
-        info.primaryCpuAbi = AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting);
-        info.secondaryCpuAbi = AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting);
+        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
+                : pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
+                : pkgSetting.getSecondaryCpuAbi();
 
         info.flags |= appInfoFlags(info.flags, pkgSetting);
         info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -710,8 +717,10 @@
 
         initForUser(info, pkg, userId);
 
-        info.primaryCpuAbi = AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting);
-        info.secondaryCpuAbi = AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting);
+        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
+                : pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
+                : pkgSetting.getSecondaryCpuAbi();
         info.nativeLibraryDir = pkg.getNativeLibraryDir();
         info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 6caddaf..f5ba3f6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -143,15 +143,6 @@
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
             throws PackageManagerException {
-        return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
-    }
-
-    /**
-     * TODO(b/135203078): Document new package parsing
-     */
-    @AnyThread
-    public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
-            List<File> frameworkSplits) throws PackageManagerException {
         var files = packageFile.listFiles();
         // Apk directory is directly nested under the current directory
         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -167,8 +158,7 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
-                frameworkSplits);
+        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
         if (result.isError()) {
             throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index f6585f6..a6f1b29 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -34,6 +34,7 @@
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
@@ -91,7 +92,7 @@
     public static SharedLibraryInfo createSharedLibraryForSdk(AndroidPackage pkg) {
         return new SharedLibraryInfo(null, pkg.getPackageName(),
                 AndroidPackageUtils.getAllCodePaths(pkg),
-                pkg.getSdkLibName(),
+                pkg.getSdkLibraryName(),
                 pkg.getSdkLibVersionMajor(),
                 SharedLibraryInfo.TYPE_SDK_PACKAGE,
                 new VersionedPackage(pkg.getManifestPackageName(),
@@ -102,7 +103,7 @@
     public static SharedLibraryInfo createSharedLibraryForStatic(AndroidPackage pkg) {
         return new SharedLibraryInfo(null, pkg.getPackageName(),
                 AndroidPackageUtils.getAllCodePaths(pkg),
-                pkg.getStaticSharedLibName(),
+                pkg.getStaticSharedLibraryName(),
                 pkg.getStaticSharedLibVersion(),
                 SharedLibraryInfo.TYPE_STATIC,
                 new VersionedPackage(pkg.getManifestPackageName(),
@@ -230,7 +231,7 @@
 
     public static boolean isLibrary(AndroidPackage pkg) {
         // TODO(b/135203078): Can parsing just enforce these always match?
-        return pkg.getSdkLibName() != null || pkg.getStaticSharedLibName() != null
+        return pkg.getSdkLibraryName() != null || pkg.getStaticSharedLibraryName() != null
                 || !pkg.getLibraryNames().isEmpty();
     }
 
@@ -271,27 +272,9 @@
         return true;
     }
 
-    public static String getPrimaryCpuAbi(AndroidPackage pkg,
-            @Nullable PackageStateInternal pkgSetting) {
-        if (pkgSetting == null || TextUtils.isEmpty(pkgSetting.getPrimaryCpuAbi())) {
-            return getRawPrimaryCpuAbi(pkg);
-        }
-
-        return pkgSetting.getPrimaryCpuAbi();
-    }
-
-    public static String getSecondaryCpuAbi(AndroidPackage pkg,
-            @Nullable PackageStateInternal pkgSetting) {
-        if (pkgSetting == null || TextUtils.isEmpty(pkgSetting.getSecondaryCpuAbi())) {
-            return getRawSecondaryCpuAbi(pkg);
-        }
-
-        return pkgSetting.getSecondaryCpuAbi();
-    }
-
     /**
      * Returns the primary ABI as parsed from the package. Used only during parsing and derivation.
-     * Otherwise prefer {@link #getPrimaryCpuAbi(AndroidPackage, PackageStateInternal)}.
+     * Otherwise prefer {@link PackageState#getPrimaryCpuAbi()}.
      */
     public static String getRawPrimaryCpuAbi(AndroidPackage pkg) {
         return ((AndroidPackageHidden) pkg).getPrimaryCpuAbi();
@@ -299,10 +282,9 @@
 
     /**
      * Returns the secondary ABI as parsed from the package. Used only during parsing and
-     * derivation. Otherwise prefer
-     * {@link #getSecondaryCpuAbi(AndroidPackage, PackageStateInternal)}.
+     * derivation. Otherwise prefer {@link PackageState#getSecondaryCpuAbi()}.
      */
-    public static String getRawSecondaryCpuAbi(AndroidPackage pkg) {
+    public static String getRawSecondaryCpuAbi(@NonNull AndroidPackage pkg) {
         return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
     }
 
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 70aca99..a43b979 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
@@ -57,6 +57,8 @@
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.AndroidPackageSplitImpl;
 import com.android.server.pm.pkg.SELinuxUtil;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ParsedActivity;
@@ -90,6 +92,7 @@
 
 import java.io.File;
 import java.security.PublicKey;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -235,11 +238,11 @@
     private Map<String, String> overlayables = emptyMap();
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
-    private String sdkLibName;
+    private String sdkLibraryName;
     private int sdkLibVersionMajor;
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
-    private String staticSharedLibName;
+    private String staticSharedLibraryName;
     private long staticSharedLibVersion;
     @NonNull
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
@@ -385,20 +388,22 @@
     @Nullable
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class)
     private Boolean requestRawExternalStorageAccess;
-    // TODO(chiuwinson): Non-null
-    @Nullable
-    private ArraySet<String> mimeGroups;
+    @NonNull
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+    private Set<String> mimeGroups = emptySet();
     // Usually there's code to set enabled to true during parsing, but it's possible to install
     // an APK targeting <R that doesn't contain an <application> tag. That code would be skipped
     // and never assign this, so initialize this to true for those cases.
     private long mBooleans = Booleans.ENABLED;
     private long mBooleans2;
-    @Nullable
-    private Set<String> mKnownActivityEmbeddingCerts;
+    @NonNull
+    private Set<String> mKnownActivityEmbeddingCerts = emptySet();
     // Derived fields
     private long mLongVersionCode;
     private int mLocaleConfigRes;
 
+    private List<AndroidPackageSplit> mSplits;
+
     @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
             @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
@@ -549,7 +554,7 @@
                 if (mimeGroups != null && mimeGroups.size() > 500) {
                     throw new IllegalStateException("Max limit on number of MIME Groups reached");
                 }
-                mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
+                mimeGroups = CollectionUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
             }
         }
     }
@@ -775,6 +780,51 @@
     }
 
     @Override
+    public List<AndroidPackageSplit> getSplits() {
+        if (mSplits == null) {
+            var splits = new ArrayList<AndroidPackageSplit>();
+            splits.add(new AndroidPackageSplitImpl(
+                    null,
+                    getBaseApkPath(),
+                    getBaseRevisionCode(),
+                    isHasCode() ? ApplicationInfo.FLAG_HAS_CODE : 0,
+                    getClassLoaderName()
+            ));
+
+            if (splitNames != null) {
+                for (int index = 0; index < splitNames.length; index++) {
+                    splits.add(new AndroidPackageSplitImpl(
+                            splitNames[index],
+                            splitCodePaths[index],
+                            splitRevisionCodes[index],
+                            splitFlags[index],
+                            splitClassLoaderNames[index]
+                    ));
+                }
+            }
+
+            if (splitDependencies != null) {
+                for (int index = 0; index < splitDependencies.size(); index++) {
+                    var splitIndex = splitDependencies.keyAt(index);
+                    var dependenciesByIndex = splitDependencies.valueAt(index);
+                    var dependencies = new ArrayList<AndroidPackageSplit>();
+                    for (int dependencyIndex : dependenciesByIndex) {
+                        // Legacy holdover, base dependencies are an array of -1 rather than empty
+                        if (dependencyIndex >= 0) {
+                            dependencies.add(splits.get(dependencyIndex));
+                        }
+                    }
+                    ((AndroidPackageSplitImpl) splits.get(splitIndex))
+                            .fillDependencies(Collections.unmodifiableList(dependencies));
+                }
+            }
+
+            mSplits = Collections.unmodifiableList(splits);
+        }
+        return mSplits;
+    }
+
+    @Override
     public String toString() {
         return "Package{"
                 + Integer.toHexString(System.identityHashCode(this))
@@ -935,8 +985,7 @@
     @NonNull
     @Override
     public Set<String> getKnownActivityEmbeddingCerts() {
-        return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
-                : mKnownActivityEmbeddingCerts;
+        return mKnownActivityEmbeddingCerts;
     }
 
     @Override
@@ -1210,8 +1259,8 @@
 
     @Nullable
     @Override
-    public String getSdkLibName() {
-        return sdkLibName;
+    public String getSdkLibraryName() {
+        return sdkLibraryName;
     }
 
     @Override
@@ -1280,8 +1329,8 @@
 
     @Nullable
     @Override
-    public String getStaticSharedLibName() {
-        return staticSharedLibName;
+    public String getStaticSharedLibraryName() {
+        return staticSharedLibraryName;
     }
 
     @Override
@@ -1949,8 +1998,7 @@
     }
 
     @Override
-    public ParsingPackage setKnownActivityEmbeddingCerts(
-            @Nullable Set<String> knownEmbeddingCerts) {
+    public ParsingPackage setKnownActivityEmbeddingCerts(@NonNull Set<String> knownEmbeddingCerts) {
         mKnownActivityEmbeddingCerts = knownEmbeddingCerts;
         return this;
     }
@@ -2220,8 +2268,8 @@
     }
 
     @Override
-    public PackageImpl setSdkLibName(String sdkLibName) {
-        this.sdkLibName = TextUtils.safeIntern(sdkLibName);
+    public PackageImpl setSdkLibraryName(String sdkLibraryName) {
+        this.sdkLibraryName = TextUtils.safeIntern(sdkLibraryName);
         return this;
     }
 
@@ -2263,8 +2311,8 @@
     }
 
     @Override
-    public PackageImpl setStaticSharedLibName(String staticSharedLibName) {
-        this.staticSharedLibName = TextUtils.safeIntern(staticSharedLibName);
+    public PackageImpl setStaticSharedLibraryName(String staticSharedLibraryName) {
+        this.staticSharedLibraryName = TextUtils.safeIntern(staticSharedLibraryName);
         return this;
     }
 
@@ -2516,7 +2564,7 @@
         appInfo.setVersionCode(mLongVersionCode);
         appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
         appInfo.setLocaleConfigRes(mLocaleConfigRes);
-        if (mKnownActivityEmbeddingCerts != null) {
+        if (!mKnownActivityEmbeddingCerts.isEmpty()) {
             appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
         }
 
@@ -2593,11 +2641,11 @@
 
     @Override
     public AndroidPackageInternal hideAsFinal() {
-        // TODO(b/135203078): Lock as immutable
         if (mStorageUuid == null) {
             assignDerivedFields();
         }
         assignDerivedFields2();
+        makeImmutable();
         return this;
     }
 
@@ -2613,6 +2661,48 @@
                 baseAppDataDir + Environment.DIR_USER_DE + systemUserSuffix);
     }
 
+    private void makeImmutable() {
+        usesLibraries = Collections.unmodifiableList(usesLibraries);
+        usesOptionalLibraries = Collections.unmodifiableList(usesOptionalLibraries);
+        usesNativeLibraries = Collections.unmodifiableList(usesNativeLibraries);
+        usesOptionalNativeLibraries = Collections.unmodifiableList(usesOptionalNativeLibraries);
+        originalPackages = Collections.unmodifiableList(originalPackages);
+        adoptPermissions = Collections.unmodifiableList(adoptPermissions);
+        requestedPermissions = Collections.unmodifiableList(requestedPermissions);
+        protectedBroadcasts = Collections.unmodifiableList(protectedBroadcasts);
+        apexSystemServices = Collections.unmodifiableList(apexSystemServices);
+
+        activities = Collections.unmodifiableList(activities);
+        receivers = Collections.unmodifiableList(receivers);
+        services = Collections.unmodifiableList(services);
+        providers = Collections.unmodifiableList(providers);
+        permissions = Collections.unmodifiableList(permissions);
+        permissionGroups = Collections.unmodifiableList(permissionGroups);
+        instrumentations = Collections.unmodifiableList(instrumentations);
+
+        overlayables = Collections.unmodifiableMap(overlayables);
+        libraryNames = Collections.unmodifiableList(libraryNames);
+        usesStaticLibraries = Collections.unmodifiableList(usesStaticLibraries);
+        usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
+        configPreferences = Collections.unmodifiableList(configPreferences);
+        reqFeatures = Collections.unmodifiableList(reqFeatures);
+        featureGroups = Collections.unmodifiableList(featureGroups);
+        usesPermissions = Collections.unmodifiableList(usesPermissions);
+        usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
+        implicitPermissions = Collections.unmodifiableList(implicitPermissions);
+        upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets);
+        keySetMapping = Collections.unmodifiableMap(keySetMapping);
+        attributions = Collections.unmodifiableList(attributions);
+        preferredActivityFilters = Collections.unmodifiableList(preferredActivityFilters);
+        processes = Collections.unmodifiableMap(processes);
+        mProperties = Collections.unmodifiableMap(mProperties);
+        queriesIntents = Collections.unmodifiableList(queriesIntents);
+        queriesPackages = Collections.unmodifiableList(queriesPackages);
+        queriesProviders = Collections.unmodifiableSet(queriesProviders);
+        mimeGroups = Collections.unmodifiableSet(mimeGroups);
+        mKnownActivityEmbeddingCerts = Collections.unmodifiableSet(mKnownActivityEmbeddingCerts);
+    }
+
     @Override
     public long getLongVersionCode() {
         return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
@@ -2937,9 +3027,9 @@
         dest.writeString(this.overlayCategory);
         dest.writeInt(this.overlayPriority);
         sForInternedStringValueMap.parcel(this.overlayables, dest, flags);
-        sForInternedString.parcel(this.sdkLibName, dest, flags);
+        sForInternedString.parcel(this.sdkLibraryName, dest, flags);
         dest.writeInt(this.sdkLibVersionMajor);
-        sForInternedString.parcel(this.staticSharedLibName, dest, flags);
+        sForInternedString.parcel(this.staticSharedLibraryName, dest, flags);
         dest.writeLong(this.staticSharedLibVersion);
         sForInternedStringList.parcel(this.libraryNames, dest, flags);
         sForInternedStringList.parcel(this.usesLibraries, dest, flags);
@@ -3041,7 +3131,7 @@
         dest.writeIntArray(this.splitRevisionCodes);
         sForBoolean.parcel(this.resizeableActivity, dest, flags);
         dest.writeInt(this.autoRevokePermissions);
-        dest.writeArraySet(this.mimeGroups);
+        sForInternedStringSet.parcel(this.mimeGroups, dest, flags);
         dest.writeInt(this.gwpAsanMode);
         dest.writeSparseIntArray(this.minExtensionVersions);
         dest.writeMap(this.mProperties);
@@ -3087,9 +3177,9 @@
         this.overlayCategory = in.readString();
         this.overlayPriority = in.readInt();
         this.overlayables = sForInternedStringValueMap.unparcel(in);
-        this.sdkLibName = sForInternedString.unparcel(in);
+        this.sdkLibraryName = sForInternedString.unparcel(in);
         this.sdkLibVersionMajor = in.readInt();
-        this.staticSharedLibName = sForInternedString.unparcel(in);
+        this.staticSharedLibraryName = sForInternedString.unparcel(in);
         this.staticSharedLibVersion = in.readLong();
         this.libraryNames = sForInternedStringList.unparcel(in);
         this.usesLibraries = sForInternedStringList.unparcel(in);
@@ -3201,7 +3291,7 @@
         this.resizeableActivity = sForBoolean.unparcel(in);
 
         this.autoRevokePermissions = in.readInt();
-        this.mimeGroups = (ArraySet<String>) in.readArraySet(boot);
+        this.mimeGroups = sForInternedStringSet.unparcel(in);
         this.gwpAsanMode = in.readInt();
         this.minExtensionVersions = in.readSparseIntArray();
         this.mProperties = in.readHashMap(boot);
@@ -3224,6 +3314,9 @@
 
         assignDerivedFields();
         assignDerivedFields2();
+
+        // Do not call makeImmutable here as cached parsing will need
+        // to mutate this instance before it's finalized.
     }
 
     @NonNull
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 8a82d6e..5108fcd 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -59,19 +58,80 @@
 import java.util.Map;
 import java.util.Set;
 
-/**
- * Explicit interface used for consumers like mainline who need a {@link SystemApi @SystemApi} form
- * of {@link AndroidPackage}.
- *
- * @hide
- */
+/** @hide */
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 @Immutable
 public interface AndroidPackage {
 
     /**
+     * Library names this package is declared as, for use by other packages with "uses-library".
+     *
+     * @see R.styleable#AndroidManifestLibrary
+     */
+    @NonNull
+    List<String> getLibraryNames();
+
+    /**
+     * @see R.styleable#AndroidManifestSdkLibrary_name
+     */
+    @Nullable
+    String getSdkLibraryName();
+
+    /**
+     * @return List of all splits for a package. Note that base.apk is considered a
+     * split and will be provided as index 0 of the list.
+     */
+    @NonNull
+    List<AndroidPackageSplit> getSplits();
+
+    /**
+     * @see R.styleable#AndroidManifestStaticLibrary_name
+     */
+    @Nullable
+    String getStaticSharedLibraryName();
+
+    /**
+     * @see ApplicationInfo#targetSdkVersion
+     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+     */
+    int getTargetSdkVersion();
+
+    /**
+     * @see ApplicationInfo#FLAG_DEBUGGABLE
+     */
+    boolean isDebuggable();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
+     */
+    boolean isIsolatedSplitLoading();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
+     */
+    boolean isSignedWithPlatformKey();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
+     */
+    boolean isUseEmbeddedDex();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
+     */
+    boolean isUsesNonSdkApi();
+
+    /**
+     * @see ApplicationInfo#FLAG_VM_SAFE_MODE
+     */
+    boolean isVmSafeMode();
+
+    // Methods below this comment are not yet exposed as API
+
+    /**
      * @see ApplicationInfo#areAttributionsUserVisible()
      * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
+     * @hide
      */
     @Nullable
     boolean areAttributionsUserVisible();
@@ -87,6 +147,7 @@
      *
      * @see ActivityInfo
      * @see PackageInfo#activities
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -97,12 +158,14 @@
      * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      *
      * @see R.styleable#AndroidManifestOriginalPackage_name
+     * @hide
      */
     @NonNull
     List<String> getAdoptPermissions();
 
     /**
      * @see R.styleable#AndroidManifestApexSystemService
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -111,10 +174,12 @@
     /**
      * @see ApplicationInfo#appComponentFactory
      * @see R.styleable#AndroidManifestApplication_appComponentFactory
+     * @hide
      */
     @Nullable
     String getAppComponentFactory();
 
+    /** @hide */
     @Immutable.Ignore
     @NonNull
     List<ParsedAttribution> getAttributions();
@@ -123,12 +188,14 @@
      * @see ApplicationInfo#AUTO_REVOKE_ALLOWED
      * @see ApplicationInfo#AUTO_REVOKE_DISCOURAGED
      * @see ApplicationInfo#AUTO_REVOKE_DISALLOWED
+     * @hide
      */
     int getAutoRevokePermissions();
 
     /**
      * @see ApplicationInfo#backupAgentName
      * @see R.styleable#AndroidManifestApplication_backupAgent
+     * @hide
      */
     @Nullable
     String getBackupAgentName();
@@ -136,30 +203,35 @@
     /**
      * @see ApplicationInfo#banner
      * @see R.styleable#AndroidManifestApplication_banner
+     * @hide
      */
     int getBanner();
 
     /**
      * @see ApplicationInfo#sourceDir
      * @see ApplicationInfo#getBaseCodePath
+     * @hide
      */
     @NonNull
     String getBaseApkPath();
 
     /**
      * @see PackageInfo#baseRevisionCode
+     * @hide
      */
     int getBaseRevisionCode();
 
     /**
      * @see ApplicationInfo#category
      * @see R.styleable#AndroidManifestApplication_appCategory
+     * @hide
      */
     int getCategory();
 
     /**
      * @see ApplicationInfo#classLoaderName
      * @see R.styleable#AndroidManifestApplication_classLoader
+     * @hide
      */
     @Nullable
     String getClassLoaderName();
@@ -167,6 +239,7 @@
     /**
      * @see ApplicationInfo#className
      * @see R.styleable#AndroidManifestApplication_name
+     * @hide
      */
     @Nullable
     String getClassName();
@@ -174,18 +247,21 @@
     /**
      * @see ApplicationInfo#compatibleWidthLimitDp
      * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+     * @hide
      */
     int getCompatibleWidthLimitDp();
 
     /**
      * @see ApplicationInfo#compileSdkVersion
      * @see R.styleable#AndroidManifest_compileSdkVersion
+     * @hide
      */
     int getCompileSdkVersion();
 
     /**
      * @see ApplicationInfo#compileSdkVersionCodename
      * @see R.styleable#AndroidManifest_compileSdkVersionCodename
+     * @hide
      */
     @Nullable
     String getCompileSdkVersionCodeName();
@@ -193,6 +269,7 @@
     /**
      * @see PackageInfo#configPreferences
      * @see R.styleable#AndroidManifestUsesConfiguration
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -201,18 +278,21 @@
     /**
      * @see ApplicationInfo#dataExtractionRulesRes
      * @see R.styleable#AndroidManifestApplication_dataExtractionRules
+     * @hide
      */
     int getDataExtractionRules();
 
     /**
      * @see ApplicationInfo#descriptionRes
      * @see R.styleable#AndroidManifestApplication_description
+     * @hide
      */
     int getDescriptionRes();
 
     /**
      * @see PackageInfo#featureGroups
      * @see R.styleable#AndroidManifestUsesFeature
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -221,12 +301,14 @@
     /**
      * @see ApplicationInfo#fullBackupContent
      * @see R.styleable#AndroidManifestApplication_fullBackupContent
+     * @hide
      */
     int getFullBackupContent();
 
     /**
      * @see ApplicationInfo#getGwpAsanMode()
      * @see R.styleable#AndroidManifestApplication_gwpAsanMode
+     * @hide
      */
     @ApplicationInfo.GwpAsanMode
     int getGwpAsanMode();
@@ -234,12 +316,14 @@
     /**
      * @see ApplicationInfo#iconRes
      * @see R.styleable#AndroidManifestApplication_icon
+     * @hide
      */
     int getIconRes();
 
     /**
      * Permissions requested but not in the manifest. These may have been split or migrated from
      * previous versions/definitions.
+     * @hide
      */
     @NonNull
     List<String> getImplicitPermissions();
@@ -247,12 +331,14 @@
     /**
      * @see ApplicationInfo#installLocation
      * @see R.styleable#AndroidManifest_installLocation
+     * @hide
      */
     int getInstallLocation();
 
     /**
      * @see InstrumentationInfo
      * @see PackageInfo#instrumentation
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -264,6 +350,7 @@
      *
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -272,6 +359,7 @@
     /**
      * @see ApplicationInfo#mKnownActivityEmbeddingCerts
      * @see R.styleable#AndroidManifestApplication_knownActivityEmbeddingCerts
+     * @hide
      */
     @SuppressWarnings("JavadocReference")
     @NonNull
@@ -280,44 +368,42 @@
     /**
      * @see ApplicationInfo#labelRes
      * @see R.styleable#AndroidManifestApplication_label
+     * @hide
      */
     int getLabelRes();
 
     /**
      * @see ApplicationInfo#largestWidthLimitDp
      * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+     * @hide
      */
     int getLargestWidthLimitDp();
 
     /**
-     * Library names this package is declared as, for use by other packages with "uses-library".
-     *
-     * @see R.styleable#AndroidManifestLibrary
-     */
-    @NonNull
-    List<String> getLibraryNames();
-
-    /**
      * The resource ID used to provide the application's locales configuration.
      *
      * @see R.styleable#AndroidManifestApplication_localeConfig
+     * @hide
      */
     int getLocaleConfigRes();
 
     /**
      * @see ApplicationInfo#logo
      * @see R.styleable#AndroidManifestApplication_logo
+     * @hide
      */
     int getLogo();
 
     /**
      * @see PackageInfo#getLongVersionCode()
+     * @hide
      */
     long getLongVersionCode();
 
     /**
      * @see ApplicationInfo#manageSpaceActivityName
      * @see R.styleable#AndroidManifestApplication_manageSpaceActivity
+     * @hide
      */
     @Nullable
     String getManageSpaceActivityName();
@@ -325,6 +411,7 @@
     /**
      * The package name as declared in the manifest, since the package can be renamed. For example,
      * static shared libs use synthetic package names.
+     * @hide
      */
     @NonNull
     String getManifestPackageName();
@@ -332,39 +419,46 @@
     /**
      * @see ApplicationInfo#maxAspectRatio
      * @see R.styleable#AndroidManifestApplication_maxAspectRatio
+     * @hide
      */
     float getMaxAspectRatio();
 
     /**
      * @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion
+     * @hide
      */
     int getMaxSdkVersion();
 
     /**
      * @see ApplicationInfo#getMemtagMode()
      * @see R.styleable#AndroidManifestApplication_memtagMode
+     * @hide
      */
     @ApplicationInfo.MemtagMode
     int getMemtagMode();
 
     /**
      * TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?)
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
     Bundle getMetaData();
 
+    /** @hide */
     @Nullable
     Set<String> getMimeGroups();
 
     /**
      * @see ApplicationInfo#minAspectRatio
      * @see R.styleable#AndroidManifestApplication_minAspectRatio
+     * @hide
      */
     float getMinAspectRatio();
 
     /**
      * @see R.styleable#AndroidManifestExtensionSdk
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -373,24 +467,28 @@
     /**
      * @see ApplicationInfo#minSdkVersion
      * @see R.styleable#AndroidManifestUsesSdk_minSdkVersion
+     * @hide
      */
     int getMinSdkVersion();
 
     /**
      * @see ApplicationInfo#getNativeHeapZeroInitialized()
      * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
+     * @hide
      */
     @ApplicationInfo.NativeHeapZeroInitialized
     int getNativeHeapZeroInitialized();
 
     /**
      * @see ApplicationInfo#nativeLibraryDir
+     * @hide
      */
     @Nullable
     String getNativeLibraryDir();
 
     /**
      * @see ApplicationInfo#nativeLibraryRootDir
+     * @hide
      */
     @Nullable
     String getNativeLibraryRootDir();
@@ -398,6 +496,7 @@
     /**
      * @see ApplicationInfo#networkSecurityConfigRes
      * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
+     * @hide
      */
     int getNetworkSecurityConfigRes();
 
@@ -407,6 +506,7 @@
      *
      * @see ApplicationInfo#nonLocalizedLabel
      * @see R.styleable#AndroidManifestApplication_label
+     * @hide
      */
     @Nullable
     CharSequence getNonLocalizedLabel();
@@ -416,6 +516,7 @@
      * available.
      *
      * @see R.styleable#AndroidManifestOriginalPackage}
+     * @hide
      */
     @NonNull
     List<String> getOriginalPackages();
@@ -423,6 +524,7 @@
     /**
      * @see PackageInfo#overlayCategory
      * @see R.styleable#AndroidManifestResourceOverlay_category
+     * @hide
      */
     @Nullable
     String getOverlayCategory();
@@ -430,12 +532,14 @@
     /**
      * @see PackageInfo#overlayPriority
      * @see R.styleable#AndroidManifestResourceOverlay_priority
+     * @hide
      */
     int getOverlayPriority();
 
     /**
      * @see PackageInfo#overlayTarget
      * @see R.styleable#AndroidManifestResourceOverlay_targetPackage
+     * @hide
      */
     @Nullable
     String getOverlayTarget();
@@ -443,24 +547,28 @@
     /**
      * @see PackageInfo#targetOverlayableName
      * @see R.styleable#AndroidManifestResourceOverlay_targetName
+     * @hide
      */
     @Nullable
     String getOverlayTargetOverlayableName();
 
     /**
      * Map of overlayable name to actor name.
+     * @hide
      */
     @NonNull
     Map<String, String> getOverlayables();
 
     /**
      * @see PackageInfo#packageName
+     * @hide
      */
     String getPackageName();
 
     /**
      * @see ApplicationInfo#scanSourceDir
      * @see ApplicationInfo#getCodePath
+     * @hide
      */
     @NonNull
     String getPath();
@@ -468,12 +576,14 @@
     /**
      * @see ApplicationInfo#permission
      * @see R.styleable#AndroidManifestApplication_permission
+     * @hide
      */
     @Nullable
     String getPermission();
 
     /**
      * @see android.content.pm.PermissionGroupInfo
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -482,6 +592,7 @@
     /**
      * @see PermissionInfo
      * @see PackageInfo#permissions
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -492,6 +603,7 @@
      * <p>
      * Map of component className to intent info inside that component. TODO(b/135203078): Is this
      * actually used/working?
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -500,12 +612,14 @@
     /**
      * @see ApplicationInfo#processName
      * @see R.styleable#AndroidManifestApplication_process
+     * @hide
      */
     @NonNull
     String getProcessName();
 
     /**
      * @see android.content.pm.ProcessInfo
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -513,6 +627,7 @@
 
     /**
      * Returns the properties set on the application
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -522,6 +637,7 @@
      * System protected broadcasts.
      *
      * @see R.styleable#AndroidManifestProtectedBroadcast
+     * @hide
      */
     @NonNull
     List<String> getProtectedBroadcasts();
@@ -537,6 +653,7 @@
      *
      * @see ProviderInfo
      * @see PackageInfo#providers
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -546,6 +663,7 @@
      * Intents that this package may query or require and thus requires visibility into.
      *
      * @see R.styleable#AndroidManifestQueriesIntent
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -555,6 +673,7 @@
      * Other packages that this package may query or require and thus requires visibility into.
      *
      * @see R.styleable#AndroidManifestQueriesPackage
+     * @hide
      */
     @NonNull
     List<String> getQueriesPackages();
@@ -563,6 +682,7 @@
      * Authorities that this package may query or require and thus requires visibility into.
      *
      * @see R.styleable#AndroidManifestQueriesProvider
+     * @hide
      */
     @NonNull
     Set<String> getQueriesProviders();
@@ -583,6 +703,7 @@
      *
      * @see ActivityInfo
      * @see PackageInfo#receivers
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -591,6 +712,7 @@
     /**
      * @see PackageInfo#reqFeatures
      * @see R.styleable#AndroidManifestUsesFeature
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -603,6 +725,7 @@
      *
      * @see PackageInfo#requestedPermissions
      * @see R.styleable#AndroidManifestUsesPermission
+     * @hide
      */
     @NonNull
     List<String> getRequestedPermissions();
@@ -610,6 +733,7 @@
     /**
      * @see PackageInfo#requiredAccountType
      * @see R.styleable#AndroidManifestApplication_requiredAccountType
+     * @hide
      */
     @Nullable
     String getRequiredAccountType();
@@ -617,6 +741,7 @@
     /**
      * @see ApplicationInfo#requiresSmallestWidthDp
      * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+     * @hide
      */
     int getRequiresSmallestWidthDp();
 
@@ -626,6 +751,7 @@
      *
      * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE
      * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE
+     * @hide
      */
     @Nullable
     Boolean getResizeableActivity();
@@ -634,6 +760,7 @@
      * SHA-512 hash of the only APK that can be used to update a system package.
      *
      * @see R.styleable#AndroidManifestRestrictUpdate
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -644,6 +771,7 @@
      *
      * @see PackageInfo#restrictedAccountType
      * @see R.styleable#AndroidManifestApplication_restrictedAccountType
+     * @hide
      */
     @Nullable
     String getRestrictedAccountType();
@@ -651,22 +779,19 @@
     /**
      * @see ApplicationInfo#roundIconRes
      * @see R.styleable#AndroidManifestApplication_roundIcon
+     * @hide
      */
     int getRoundIconRes();
 
     /**
-     * @see R.styleable#AndroidManifestSdkLibrary_name
-     */
-    @Nullable
-    String getSdkLibName();
-
-    /**
      * @see R.styleable#AndroidManifestSdkLibrary_versionMajor
+     * @hide
      */
     int getSdkLibVersionMajor();
 
     /**
      * @see ApplicationInfo#secondaryNativeLibraryDir
+     * @hide
      */
     @Nullable
     String getSecondaryNativeLibraryDir();
@@ -682,6 +807,7 @@
      *
      * @see ServiceInfo
      * @see PackageInfo#services
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -690,6 +816,7 @@
     /**
      * @see PackageInfo#sharedUserId
      * @see R.styleable#AndroidManifest_sharedUserId
+     * @hide
      */
     @Nullable
     String getSharedUserId();
@@ -697,12 +824,14 @@
     /**
      * @see PackageInfo#sharedUserLabel
      * @see R.styleable#AndroidManifest_sharedUserLabel
+     * @hide
      */
     int getSharedUserLabel();
 
     /**
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -711,6 +840,7 @@
     /**
      * @see ApplicationInfo#splitClassLoaderNames
      * @see R.styleable#AndroidManifestApplication_classLoader
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -719,6 +849,7 @@
     /**
      * @see ApplicationInfo#splitSourceDirs
      * @see ApplicationInfo#getSplitCodePaths
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -726,6 +857,7 @@
 
     /**
      * @see ApplicationInfo#splitDependencies
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -733,6 +865,7 @@
 
     /**
      * Flags of any split APKs; ordered by parsed splitName
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -743,6 +876,7 @@
      *
      * @see ApplicationInfo#splitNames
      * @see PackageInfo#splitNames
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -750,37 +884,29 @@
 
     /**
      * @see PackageInfo#splitRevisionCodes
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
     int[] getSplitRevisionCodes();
 
     /**
-     * @see R.styleable#AndroidManifestStaticLibrary_name
-     */
-    @Nullable
-    String getStaticSharedLibName();
-
-    /**
      * @see R.styleable#AndroidManifestStaticLibrary_version
+     * @hide
      */
     long getStaticSharedLibVersion();
 
     /**
      * @see ApplicationInfo#targetSandboxVersion
      * @see R.styleable#AndroidManifest_targetSandboxVersion
+     * @hide
      */
     int getTargetSandboxVersion();
 
     /**
-     * @see ApplicationInfo#targetSdkVersion
-     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
-     */
-    int getTargetSdkVersion();
-
-    /**
      * @see ApplicationInfo#taskAffinity
      * @see R.styleable#AndroidManifestApplication_taskAffinity
+     * @hide
      */
     @Nullable
     String getTaskAffinity();
@@ -788,12 +914,14 @@
     /**
      * @see ApplicationInfo#theme
      * @see R.styleable#AndroidManifestApplication_theme
+     * @hide
      */
     int getTheme();
 
     /**
      * @see ApplicationInfo#uiOptions
      * @see R.styleable#AndroidManifestApplication_uiOptions
+     * @hide
      */
     int getUiOptions();
 
@@ -802,6 +930,7 @@
      * {@link android.os.UserHandle#SYSTEM}.
      *
      * @deprecated Use {@link PackageState#getAppId()} instead.
+     * @hide
      */
     @Deprecated
     int getUid();
@@ -811,18 +940,21 @@
      * ParsingPackageUtils#TAG_KEY_SETS}.
      *
      * @see R.styleable#AndroidManifestUpgradeKeySet
+     * @hide
      */
     @NonNull
     Set<String> getUpgradeKeySets();
 
     /**
      * @see R.styleable#AndroidManifestUsesLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesLibraries();
 
     /**
      * @see R.styleable#AndroidManifestUsesNativeLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesNativeLibraries();
@@ -833,6 +965,7 @@
      * absence manually.
      *
      * @see R.styleable#AndroidManifestUsesLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesOptionalLibraries();
@@ -843,10 +976,12 @@
      * handle absence manually.
      *
      * @see R.styleable#AndroidManifestUsesNativeLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesOptionalNativeLibraries();
 
+    /** @hide */
     @Immutable.Ignore
     @NonNull
     List<ParsedUsesPermission> getUsesPermissions();
@@ -855,12 +990,14 @@
      * TODO(b/135203078): Move SDK library stuff to an inner data class
      *
      * @see R.styleable#AndroidManifestUsesSdkLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesSdkLibraries();
 
     /**
      * @see R.styleable#AndroidManifestUsesSdkLibrary_certDigest
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -868,6 +1005,7 @@
 
     /**
      * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -877,12 +1015,14 @@
      * TODO(b/135203078): Move static library stuff to an inner data class
      *
      * @see R.styleable#AndroidManifestUsesStaticLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesStaticLibraries();
 
     /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary_certDigest
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -890,6 +1030,7 @@
 
     /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary_version
+     * @hide
      */
     @Immutable.Ignore
     @Nullable
@@ -897,60 +1038,72 @@
 
     /**
      * @see PackageInfo#versionName
+     * @hide
      */
     @Nullable
     String getVersionName();
 
     /**
      * @see ApplicationInfo#volumeUuid
+     * @hide
      */
     @Nullable
     String getVolumeUuid();
 
+    /** @hide */
     @Nullable
     String getZygotePreloadName();
 
+    /** @hide */
     boolean hasPreserveLegacyExternalStorage();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION
      * @see R.styleable#AndroidManifestApplication_requestForegroundServiceExemption
+     * @hide
      */
     boolean hasRequestForegroundServiceExemption();
 
     /**
      * @see ApplicationInfo#getRequestRawExternalStorageAccess()
      * @see R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess
+     * @hide
      */
     Boolean hasRequestRawExternalStorageAccess();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
+     * @hide
      */
     boolean isAllowAudioPlaybackCapture();
 
     /**
      * @see ApplicationInfo#FLAG_ALLOW_BACKUP
+     * @hide
      */
     boolean isAllowBackup();
 
     /**
      * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
+     * @hide
      */
     boolean isAllowClearUserData();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
+     * @hide
      */
     boolean isAllowClearUserDataOnFailedRestore();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+     * @hide
      */
     boolean isAllowNativeHeapPointerTagging();
 
     /**
      * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
+     * @hide
      */
     boolean isAllowTaskReparenting();
 
@@ -960,115 +1113,126 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
      * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
+     * @hide
      */
     boolean isAnyDensity();
 
+    /** @hide */
     boolean isApex();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
+     * @hide
      */
     boolean isBackupInForeground();
 
     /**
      * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
+     * @hide
      */
     boolean isBaseHardwareAccelerated();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
+     * @hide
      */
     boolean isCantSaveState();
 
     /**
      * @see PackageInfo#coreApp
+     * @hide
      */
     boolean isCoreApp();
 
     /**
      * @see ApplicationInfo#crossProfile
+     * @hide
      */
     boolean isCrossProfile();
 
     /**
-     * @see ApplicationInfo#FLAG_DEBUGGABLE
-     */
-    boolean isDebuggable();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
+     * @hide
      */
     boolean isDefaultToDeviceProtectedStorage();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
+     * @hide
      */
     boolean isDirectBootAware();
 
     /**
      * @see ApplicationInfo#enabled
      * @see R.styleable#AndroidManifestApplication_enabled
+     * @hide
      */
     boolean isEnabled();
 
     /**
      * @see ApplicationInfo#FLAG_EXTERNAL_STORAGE
+     * @hide
      */
     boolean isExternalStorage();
 
     /**
      * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
+     * @hide
      */
     boolean isExtractNativeLibs();
 
     /**
      * @see ApplicationInfo#FLAG_FACTORY_TEST
+     * @hide
      */
     boolean isFactoryTest();
 
     /**
      * @see R.styleable#AndroidManifestApplication_forceQueryable
+     * @hide
      */
     boolean isForceQueryable();
 
     /**
      * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
+     * @hide
      */
     boolean isFullBackupOnly();
 
     /**
      * @see ApplicationInfo#FLAG_IS_GAME
+     * @hide
      */
     @Deprecated
     boolean isGame();
 
     /**
      * @see ApplicationInfo#FLAG_HAS_CODE
+     * @hide
      */
     boolean isHasCode();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS
+     * @hide
      */
     boolean isHasDomainUrls();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
+     * @hide
      */
     boolean isHasFragileUserData();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
-     */
-    boolean isIsolatedSplitLoading();
-
-    /**
      * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
+     * @hide
      */
     boolean isKillAfterRestore();
 
     /**
      * @see ApplicationInfo#FLAG_LARGE_HEAP
+     * @hide
      */
     boolean isLargeHeap();
 
@@ -1077,83 +1241,99 @@
      * smaller than the current SDK version.
      *
      * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
+     * @hide
      */
     boolean isLeavingSharedUid();
 
     /**
      * @see ApplicationInfo#FLAG_MULTIARCH
+     * @hide
      */
     boolean isMultiArch();
 
     /**
      * @see ApplicationInfo#nativeLibraryRootRequiresIsa
+     * @hide
      */
     boolean isNativeLibraryRootRequiresIsa();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_ODM
+     * @hide
      */
     boolean isOdm();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_OEM
+     * @hide
      */
     boolean isOem();
 
     /**
      * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
+     * @hide
      */
     boolean isOnBackInvokedCallbackEnabled();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY
      * @see ApplicationInfo#isResourceOverlay()
+     * @hide
      */
     boolean isOverlay();
 
     /**
      * @see PackageInfo#mOverlayIsStatic
+     * @hide
      */
     boolean isOverlayIsStatic();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE
+     * @hide
      */
     boolean isPartiallyDirectBootAware();
 
     /**
      * @see ApplicationInfo#FLAG_PERSISTENT
+     * @hide
      */
     boolean isPersistent();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_PRIVILEGED
+     * @hide
      */
     boolean isPrivileged();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_PRODUCT
+     * @hide
      */
     boolean isProduct();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
+     * @hide
      */
     boolean isProfileable();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
+     * @hide
      */
     boolean isProfileableByShell();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
+     * @hide
      */
     boolean isRequestLegacyExternalStorage();
 
     /**
      * @see PackageInfo#requiredForAllUsers
      * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
+     * @hide
      */
     boolean isRequiredForAllUsers();
 
@@ -1162,6 +1342,7 @@
      * when the application's user data is cleared.
      *
      * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
+     * @hide
      */
     boolean isResetEnabledSettingsOnAppDataCleared();
 
@@ -1171,36 +1352,37 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_resizeable
      * @see ApplicationInfo#FLAG_RESIZEABLE_FOR_SCREENS
+     * @hide
      */
     boolean isResizeable();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION
+     * @hide
      */
     boolean isResizeableActivityViaSdkVersion();
 
     /**
      * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
+     * @hide
      */
     boolean isRestoreAnyVersion();
 
     /**
      * True means that this package/app contains an SDK library.
+     * @hide
      */
     boolean isSdkLibrary();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
-     */
-    boolean isSignedWithPlatformKey();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_STATIC_SHARED_LIBRARY
+     * @hide
      */
     boolean isStaticSharedLibrary();
 
     /**
      * @see PackageInfo#isStub
+     * @hide
      */
     boolean isStub();
 
@@ -1210,6 +1392,7 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
      * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
+     * @hide
      */
     boolean isSupportsExtraLargeScreens();
 
@@ -1219,6 +1402,7 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
      * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
+     * @hide
      */
     boolean isSupportsLargeScreens();
 
@@ -1227,11 +1411,13 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
      * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
+     * @hide
      */
     boolean isSupportsNormalScreens();
 
     /**
      * @see ApplicationInfo#FLAG_SUPPORTS_RTL
+     * @hide
      */
     boolean isSupportsRtl();
 
@@ -1241,21 +1427,25 @@
      *
      * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
      * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
+     * @hide
      */
     boolean isSupportsSmallScreens();
 
     /**
      * @see ApplicationInfo#FLAG_SYSTEM
+     * @hide
      */
     boolean isSystem();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_SYSTEM_EXT
+     * @hide
      */
     boolean isSystemExt();
 
     /**
      * @see ApplicationInfo#FLAG_TEST_ONLY
+     * @hide
      */
     boolean isTestOnly();
 
@@ -1265,26 +1455,19 @@
      * cpuAbiOverride is also set.
      *
      * @see R.attr#use32bitAbi
+     * @hide
      */
     boolean isUse32BitAbi();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
-     */
-    boolean isUseEmbeddedDex();
-
-    /**
      * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
+     * @hide
      */
     boolean isUsesCleartextTraffic();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
-     */
-    boolean isUsesNonSdkApi();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_VENDOR
+     * @hide
      */
     boolean isVendor();
 
@@ -1294,11 +1477,7 @@
      * @see R.styleable#AndroidManifestActivity_visibleToInstantApps
      * @see R.styleable#AndroidManifestProvider_visibleToInstantApps
      * @see R.styleable#AndroidManifestService_visibleToInstantApps
+     * @hide
      */
     boolean isVisibleToInstantApps();
-
-    /**
-     * @see ApplicationInfo#FLAG_VM_SAFE_MODE
-     */
-    boolean isVmSafeMode();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
new file mode 100644
index 0000000..a17ecc3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.processor.immutability.Immutable;
+
+import java.util.List;
+
+/** @hide */
+@Immutable
+public interface AndroidPackageSplit {
+
+    @Nullable
+    String getName();
+
+    @NonNull
+    String getPath();
+
+    int getRevisionCode();
+
+    boolean isHasCode();
+
+    @Nullable
+    String getClassLoaderName();
+
+    @NonNull
+    List<AndroidPackageSplit> getDependencies();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
new file mode 100644
index 0000000..9aac8a8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.content.pm.ApplicationInfo;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class AndroidPackageSplitImpl implements AndroidPackageSplit {
+
+    @Nullable
+    private final String mName;
+    @NonNull
+    private final String mPath;
+    private final int mRevisionCode;
+    private final int mFlags;
+    @Nullable
+    private final String mClassLoaderName;
+
+    @NonNull
+    private List<AndroidPackageSplit> mDependencies = Collections.emptyList();
+
+    public AndroidPackageSplitImpl(@Nullable String name, @NonNull String path, int revisionCode,
+            int flags, @Nullable String classLoaderName) {
+        mName = name;
+        mPath = path;
+        mRevisionCode = revisionCode;
+        mFlags = flags;
+        mClassLoaderName = classLoaderName;
+    }
+
+    public void fillDependencies(@NonNull List<AndroidPackageSplit> splits) {
+        if (!mDependencies.isEmpty()) {
+            throw new IllegalStateException("Cannot fill split dependencies more than once");
+        }
+        mDependencies = splits;
+    }
+
+    @Nullable
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    @NonNull
+    @Override
+    public String getPath() {
+        return mPath;
+    }
+
+    @Override
+    public int getRevisionCode() {
+        return mRevisionCode;
+    }
+
+    @Override
+    public boolean isHasCode() {
+        return (mFlags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+    }
+
+    @Nullable
+    @Override
+    public String getClassLoaderName() {
+        return mClassLoaderName;
+    }
+
+    @NonNull
+    @Override
+    public List<AndroidPackageSplit> getDependencies() {
+        return mDependencies;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AndroidPackageSplitImpl)) return false;
+        AndroidPackageSplitImpl that = (AndroidPackageSplitImpl) o;
+        var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags && Objects.equals(
+                mName, that.mName) && Objects.equals(mPath, that.mPath)
+                && Objects.equals(mClassLoaderName, that.mClassLoaderName);
+
+        if (!fieldsEqual) return false;
+        if (mDependencies.size() != that.mDependencies.size()) return false;
+
+        // Should be impossible, but to avoid circular dependencies,
+        // only search 1 level deep using split name
+        for (int index = 0; index < mDependencies.size(); index++) {
+            if (!Objects.equals(mDependencies.get(index).getName(),
+                    that.mDependencies.get(index).getName())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        // Should be impossible, but to avoid circular dependencies,
+        // only search 1 level deep using split name
+        var dependenciesHash = Objects.hash(mName, mPath, mRevisionCode, mFlags, mClassLoaderName);
+        for (int index = 0; index < mDependencies.size(); index++) {
+            var name = mDependencies.get(index).getName();
+            dependenciesHash = 31 * dependenciesHash + (name == null ? 0 : name.hashCode());
+        }
+        return dependenciesHash;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 12e9671..fa1a63f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -21,37 +21,20 @@
 import android.annotation.Size;
 import android.annotation.UserIdInt;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningInfo;
+import android.os.UserHandle;
 import android.processor.immutability.Immutable;
 import android.util.SparseArray;
 
-import com.android.server.pm.PackageSetting;
-import com.android.server.pm.Settings;
-
 import java.io.File;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * The API surface for a {@link PackageSetting}. Methods are expected to return immutable objects.
- * This may mean copying data on each invocation until related classes are refactored to be
- * immutable.
- * <p>
- * Note that until immutability or read-only caching is enabled, {@link PackageSetting} cannot be
- * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data. This is a
- * relatively expensive operation since it has to create an object for every package, but it's much
- * lighter than the alternative of generating {@link PackageInfo} objects.
- * <p>
- * TODO: Documentation TODO: Currently missing, should be exposed as API?
- * <ul>
- *     <li>keySetData</li>
- *     <li>installSource</li>
- *     <li>incrementalStates</li>
- * </ul>
+ * The exposed system server process API for package data, shared with the internal
+ * PackageManagerService implementation. All returned data is guaranteed immutable.
  *
  * @hide
  */
@@ -59,97 +42,65 @@
 @Immutable
 public interface PackageState {
 
+    /*
+     * Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be
+     * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data.
+     * This is a relatively expensive operation since it has to create an object for every package,
+     * but it's much lighter than the alternative of generating {@link PackageInfo} objects.
+     * <p>
+     * TODO: Documentation
+     * TODO: Currently missing, should be exposed as API?
+     *   - keySetData
+     *   - installSource
+     *   - incrementalStates
+     */
+
+    // Non-doc comment invisible to API consumers:
+    // Guidelines:
+    //  - All return values should prefer non-null, immutable interfaces with only exposed getters
+    //    - Unless null itself communicates something important
+    //    - If the type is a Java collection type, it must be wrapped with unmodifiable
+    //  - All type names must be non-suffixed, with any internal types being refactored to suffix
+    //    with _Internal as necessary
+    //  - No exposure of raw values that are overridden during parsing, such as CPU ABI
+    //  - Mirroring another available system or public API is not enough justification to violate
+    //    these guidelines
+
     /**
      * This can be null whenever a physical APK on device is missing. This can be the result of
      * removing an external storage device where the APK resides.
-     * <p>
-     * This will result in the system reading the {@link PackageSetting} from disk, but without
-     * being able to parse the base APK's AndroidManifest.xml to read all of its metadata. The data
-     * that is written and read in {@link Settings} includes a minimal set of metadata needed to
-     * perform other checks in the system.
-     * <p>
+     * <p/>
+     * This will result in the system reading the state from disk, but without being able to parse
+     * the base APK's AndroidManifest.xml to read all of its metadata. The only available data that
+     * is written and read is the minimal set required to perform other checks in the system.
+     * <p/>
      * This is important in order to enforce uniqueness within the system, as the package, even if
      * on a removed storage device, is still considered installed. Another package of the same
      * application ID or declaring the same permissions or similar cannot be installed.
-     * <p>
+     * <p/>
      * Re-attaching the storage device to make the APK available should allow the user to use the
      * app once the device reboots or otherwise re-scans it.
+     * <p/>
+     * This can also occur in an device OTA situation where the package is no longer parsable on
+     * an updated SDK version, causing it to be rejected, but the state associated with it retained,
+     * similarly to if the package had been uninstalled with the --keep-data option.
      */
     @Nullable
     AndroidPackage getAndroidPackage();
 
     /**
      * The non-user-specific UID, or the UID if the user ID is
-     * {@link android.os.UserHandle#USER_SYSTEM}.
+     * {@link android.os.UserHandle#SYSTEM}.
      */
     int getAppId();
 
     /**
-     * Value set through {@link PackageManager#setApplicationCategoryHint(String, int)}. Only
-     * applied if the application itself does not declare a category.
-     *
-     * @see AndroidPackage#getCategory()
-     */
-    int getCategoryOverride();
-
-    /**
-     * The install time CPU override, if any. This value is written at install time
-     * and doesn't change during the life of an install. If non-null,
-     * {@link #getPrimaryCpuAbi()} will also contain the same value.
-     */
-    @Nullable
-    String getCpuAbiOverride();
-
-    /**
-     * In epoch milliseconds. The last modified time of the file directory which houses the app
-     * APKs. Only updated on package update; does not track realtime modifications.
-     */
-    long getLastModifiedTime();
-
-    /**
-     * An aggregation across the framework of the last time an app was used for a particular reason.
-     * Keys are indexes into the array represented by {@link PackageManager.NotifyReason}, values
-     * are in epoch milliseconds.
-     */
-    @Immutable.Ignore
-    @Size(PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT)
-    @NonNull
-    long[] getLastPackageUsageTime();
-
-    /**
-     * In epoch milliseconds. The timestamp of the last time the package on device went through
-     * an update package installation.
-     */
-    long getLastUpdateTime();
-
-    /**
-     * Cached here in case the physical code directory on device is unmounted.
-     * @see AndroidPackage#getLongVersionCode()
-     */
-    long getVersionCode();
-
-    /**
-     * Maps mime group name to the set of Mime types in a group. Mime groups declared by app are
-     * populated with empty sets at construction. Mime groups can not be created/removed at runtime,
-     * thus keys in this map should not change.
-     */
-    @NonNull
-    Map<String, Set<String>> getMimeGroups();
-
-    /**
      * @see AndroidPackage#getPackageName()
      */
     @NonNull
     String getPackageName();
 
     /**
-     * TODO: Rename this to getCodePath
-     * @see AndroidPackage#getPath()
-     */
-    @NonNull
-    File getPath();
-
-    /**
      * @see ApplicationInfo#primaryCpuAbi
      */
     @Nullable
@@ -162,7 +113,109 @@
     String getSecondaryCpuAbi();
 
     /**
+     * @see AndroidPackage#isPrivileged()
+     */
+    boolean isPrivileged();
+
+    /**
+     * @see AndroidPackage#isSystem()
+     */
+    boolean isSystem();
+
+    /**
+     * Whether this app is on the /data partition having been upgraded from a preinstalled app on a
+     * system partition.
+     */
+    boolean isUpdatedSystemApp();
+
+    /**
+     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+     */
+    @NonNull
+    PackageUserState getStateForUser(@NonNull UserHandle user);
+
+    /**
+     * @see R.styleable#AndroidManifestUsesLibrary
+     */
+    @NonNull
+    List<SharedLibrary> getUsesLibraries();
+
+    // Methods below this comment are not yet exposed as API
+
+    /**
+     * Value set through {@link PackageManager#setApplicationCategoryHint(String, int)}. Only
+     * applied if the application itself does not declare a category.
+     *
+     * @see AndroidPackage#getCategory()
+     * @hide
+     */
+    int getCategoryOverride();
+
+    /**
+     * The install time CPU override, if any. This value is written at install time
+     * and doesn't change during the life of an install. If non-null,
+     * {@link #getPrimaryCpuAbiLegacy()} will also contain the same value.
+     *
+     * @hide
+     */
+    @Nullable
+    String getCpuAbiOverride();
+
+    /**
+     * In epoch milliseconds. The last modified time of the file directory which houses the app
+     * APKs. Only updated on package update; does not track realtime modifications.
+     *
+     * @hide
+     */
+    long getLastModifiedTime();
+
+    /**
+     * An aggregation across the framework of the last time an app was used for a particular reason.
+     * Keys are indexes into the array represented by {@link PackageManager.NotifyReason}, values
+     * are in epoch milliseconds.
+     *
+     * @hide
+     */
+    @Immutable.Ignore
+    @Size(PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT)
+    @NonNull
+    long[] getLastPackageUsageTime();
+
+    /**
+     * In epoch milliseconds. The timestamp of the last time the package on device went through
+     * an update package installation.
+     *
+     * @hide
+     */
+    long getLastUpdateTime();
+
+    /**
+     * Cached here in case the physical code directory on device is unmounted.
+     * @see AndroidPackage#getLongVersionCode()
+     * @hide
+     */
+    long getVersionCode();
+
+    /**
+     * Maps mime group name to the set of Mime types in a group. Mime groups declared by app are
+     * populated with empty sets at construction. Mime groups can not be created/removed at runtime,
+     * thus keys in this map should not change.
+     *
+     * @hide
+     */
+    @NonNull
+    Map<String, Set<String>> getMimeGroups();
+
+    /**
+     * @see AndroidPackage#getPath()
+     * @hide
+     */
+    @NonNull
+    File getPath();
+
+    /**
      * Whether the package shares the same user ID as other packages
+     * @hide
      */
     boolean hasSharedUser();
 
@@ -172,13 +225,16 @@
      *
      * @return the app ID of the shared user that this package is a part of, or -1 if it's not part
      * of a shared user.
+     * @hide
      */
     int getSharedUserAppId();
 
+    /** @hide */
     @Immutable.Ignore
     @NonNull
     SigningInfo getSigningInfo();
 
+    /** @hide */
     @Immutable.Ignore
     @NonNull
     SparseArray<? extends PackageUserState> getUserStates();
@@ -186,6 +242,7 @@
     /**
      * @return the result of {@link #getUserStates()}.get(userId) or
      * {@link PackageUserState#DEFAULT} if the state doesn't exist.
+     * @hide
      */
     @NonNull
     default PackageUserState getUserStateOrDefault(@UserIdInt int userId) {
@@ -197,19 +254,14 @@
      * The actual files resolved for each shared library.
      *
      * @see R.styleable#AndroidManifestUsesLibrary
+     * @hide
      */
     @NonNull
     List<String> getUsesLibraryFiles();
 
     /**
-     * @see R.styleable#AndroidManifestUsesLibrary
-     */
-    @Immutable.Ignore
-    @NonNull
-    List<SharedLibraryInfo> getUsesLibraryInfos();
-
-    /**
      * @see R.styleable#AndroidManifestUsesSdkLibrary
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -217,6 +269,7 @@
 
     /**
      * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -224,6 +277,7 @@
 
     /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -231,6 +285,7 @@
 
     /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary_version
+     * @hide
      */
     @Immutable.Ignore
     @NonNull
@@ -238,18 +293,22 @@
 
     /**
      * @see AndroidPackage#getVolumeUuid()
+     * @hide
      */
     @Nullable
     String getVolumeUuid();
 
     /**
      * @see AndroidPackage#isExternalStorage()
+     * @hide
      */
     boolean isExternalStorage();
 
     /**
      * Whether a package was installed --force-queryable such that it is always queryable by any
      * package, regardless of their manifest content.
+     *
+     * @hide
      */
     boolean isForceQueryableOverride();
 
@@ -258,67 +317,62 @@
      *
      * @see PackageManager#MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
      * @see PackageManager#setSystemAppState
+     * @hide
      */
     boolean isHiddenUntilInstalled();
 
     /**
      * @see com.android.server.pm.permission.UserPermissionState
+     * @hide
      */
     boolean isInstallPermissionsFixed();
 
     /**
      * @see AndroidPackage#isOdm()
+     * @hide
      */
     boolean isOdm();
 
     /**
      * @see AndroidPackage#isOem()
+     * @hide
      */
     boolean isOem();
 
     /**
-     * @see AndroidPackage#isPrivileged()
-     */
-    boolean isPrivileged();
-
-    /**
      * @see AndroidPackage#isProduct()
+     * @hide
      */
     boolean isProduct();
 
     /**
      * @see ApplicationInfo#PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER
+     * @hide
      */
     boolean isRequiredForSystemUser();
 
     /**
-     * @see AndroidPackage#isSystem()
-     */
-    boolean isSystem();
-
-    /**
      * @see AndroidPackage#isSystemExt()
+     * @hide
      */
     boolean isSystemExt();
 
     /**
      * Whether or not an update is available. Ostensibly only for instant apps.
+     * @hide
      */
     boolean isUpdateAvailable();
 
     /**
-     * Whether this app is on the /data partition having been upgraded from a preinstalled app on a
-     * system partition.
-     */
-    boolean isUpdatedSystemApp();
-
-    /**
      * Whether this app is packaged in an updated apex.
+     *
+     * @hide
      */
     boolean isApkInUpdatedApex();
 
     /**
      * @see AndroidPackage#isVendor()
+     * @hide
      */
     boolean isVendor();
 }
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 eac0842..c6ce40e 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -21,9 +21,9 @@
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -33,6 +33,7 @@
 import com.android.server.pm.Settings;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -139,7 +140,7 @@
     @NonNull
     private final long[] mUsesStaticLibrariesVersions;
     @NonNull
-    private final List<SharedLibraryInfo> mUsesLibraryInfos;
+    private final List<SharedLibrary> mUsesLibraries;
     @NonNull
     private final List<String> mUsesLibraryFiles;
     @NonNull
@@ -170,7 +171,7 @@
         mLastModifiedTime = pkgState.getLastModifiedTime();
         mLastUpdateTime = pkgState.getLastUpdateTime();
         mLongVersionCode = pkgState.getVersionCode();
-        mMimeGroups = pkgState.getMimeGroups();
+        mMimeGroups = Collections.unmodifiableMap(pkgState.getMimeGroups());
         mPath = pkgState.getPath();
         mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
         mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
@@ -180,8 +181,8 @@
         mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
         mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
         mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
-        mUsesLibraryInfos = pkgState.getUsesLibraryInfos();
-        mUsesLibraryFiles = pkgState.getUsesLibraryFiles();
+        mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
+        mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
         setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
         setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
         setBoolean(Booleans.INSTALL_PERMISSIONS_FIXED, pkgState.isInstallPermissionsFixed());
@@ -195,11 +196,18 @@
         int userStatesSize = userStates.size();
         mUserStates = new SparseArray<>(userStatesSize);
         for (int index = 0; index < userStatesSize; index++) {
-            mUserStates.put(mUserStates.keyAt(index),
-                    UserStateImpl.copy(mUserStates.valueAt(index)));
+            mUserStates.put(userStates.keyAt(index),
+                    UserStateImpl.copy(userStates.valueAt(index)));
         }
     }
 
+    @NonNull
+    @Override
+    public PackageUserState getStateForUser(@NonNull UserHandle user) {
+        PackageUserState userState = getUserStates().get(user.getIdentifier());
+        return userState == null ? PackageUserState.DEFAULT : userState;
+    }
+
     @Override
     public boolean isExternalStorage() {
         return getBoolean(Booleans.EXTERNAL_STORAGE);
@@ -468,8 +476,7 @@
         }
 
         @DataClass.Generated.Member
-        public @NonNull
-        ArraySet<String> getDisabledComponents() {
+        public @NonNull ArraySet<String> getDisabledComponents() {
             return mDisabledComponents;
         }
 
@@ -535,10 +542,10 @@
         }
 
         @DataClass.Generated(
-                time = 1644270981508L,
+                time = 1661977809886L,
                 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 java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<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 mFirstInstallTime\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  long mFirstInstallTime\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() {}
 
@@ -659,8 +666,8 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
-        return mUsesLibraryInfos;
+    public @NonNull List<SharedLibrary> getUsesLibraries() {
+        return mUsesLibraries;
     }
 
     @DataClass.Generated.Member
@@ -690,10 +697,10 @@
     }
 
     @DataClass.Generated(
-            time = 1644270981543L,
+            time = 1661977809932L,
             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  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  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<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\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\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 @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 isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\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\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            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  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  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\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 @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 int getSharedUserAppId()\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)")
     @Deprecated
     private void __metadata() {}
 
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 84799ea..2f4c0277 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -31,6 +31,7 @@
 
 /**
  * Exposes internal types for internal usage of {@link PackageState}.
+ * @hide
  */
 public interface PackageStateInternal extends PackageState {
 
@@ -82,4 +83,24 @@
 
     @NonNull
     PackageKeySetData getKeySetData();
+
+    /**
+     * Return the exact value stored inside this object for the primary CPU ABI type. This does
+     * not fallback to the inner {@link #getAndroidPackage()}, unlike {@link #getPrimaryCpuAbi()}.
+     *
+     * @deprecated Use {@link #getPrimaryCpuAbi()} if at all possible.
+     *
+     * TODO(b/249779400): Remove and see if the fallback-only API is a usable replacement
+     */
+    @Deprecated
+    @Nullable
+    String getPrimaryCpuAbiLegacy();
+
+    /**
+     * Same behavior as {@link #getPrimaryCpuAbiLegacy()}, but with the secondary ABI.
+     *
+     * @deprecated Use {@link #getSecondaryCpuAbi()} if at all possible.
+     */
+    @Nullable
+    String getSecondaryCpuAbiLegacy();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index 2c8b977..b22c038 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -23,12 +23,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.server.pm.PackageSetting;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * For use by {@link PackageSetting} to maintain functionality that used to exist in PackageParser.
@@ -38,15 +38,16 @@
  *
  * These fields are also not copied into any cloned PackageSetting, to preserve the old behavior
  * where they would be lost implicitly by re-generating the package object.
+ * @hide
  */
 @DataClass(genSetters = true, genConstructor = false, genBuilder = false)
-@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting"})
+@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting", "setUsesLibraryInfos"})
 public class PackageStateUnserialized {
 
     private boolean hiddenUntilInstalled;
 
     @NonNull
-    private List<SharedLibraryInfo> usesLibraryInfos = emptyList();
+    private List<SharedLibraryWrapper> usesLibraryInfos = emptyList();
 
     @NonNull
     private List<String> usesLibraryFiles = emptyList();
@@ -69,6 +70,18 @@
         mPackageSetting = packageSetting;
     }
 
+    @NonNull
+    public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryWrapper value) {
+        usesLibraryInfos = CollectionUtils.add(usesLibraryInfos, value);
+        return this;
+    }
+
+    @NonNull
+    public PackageStateUnserialized addUsesLibraryFile(@NonNull String value) {
+        usesLibraryFiles = CollectionUtils.add(usesLibraryFiles, value);
+        return this;
+    }
+
     private long[] lazyInitLastPackageUsageTimeInMills() {
         return new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT];
     }
@@ -129,8 +142,16 @@
     }
 
     public @NonNull List<SharedLibraryInfo> getNonNativeUsesLibraryInfos() {
-        return getUsesLibraryInfos().stream()
-                .filter((l) -> !l.isNative()).collect(Collectors.toList());
+        var list = new ArrayList<SharedLibraryInfo>();
+        usesLibraryInfos = getUsesLibraryInfos();
+        for (int index = 0; index < usesLibraryInfos.size(); index++) {
+            var library = usesLibraryInfos.get(index);
+            if (!library.isNative()) {
+                list.add(library.getInfo());
+            }
+
+        }
+        return list;
     }
 
     public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
@@ -140,7 +161,11 @@
     }
 
     public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
-        usesLibraryInfos = value;
+        var list = new ArrayList<SharedLibraryWrapper>();
+        for (int index = 0; index < value.size(); index++) {
+            list.add(new SharedLibraryWrapper(value.get(index)));
+        }
+        usesLibraryInfos = list;
         mPackageSetting.onChanged();
         return this;
     }
@@ -202,7 +227,7 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
+    public @NonNull List<SharedLibraryWrapper> getUsesLibraryInfos() {
         return usesLibraryInfos;
     }
 
@@ -251,10 +276,10 @@
     }
 
     @DataClass.Generated(
-            time = 1646203523807L,
+            time = 1661373697219L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
-            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
index a883a05..9749cfa 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
@@ -24,6 +24,7 @@
 
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 
+/** @hide */
 public class PackageStateUtils {
 
     public static boolean isMatch(PackageState packageState, long flags) {
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 2d25385..a68e59b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -26,133 +26,173 @@
 
 import java.util.Map;
 
+
 /**
- * The API surface for {@link PackageUserStateInternal}, for use by in-process mainline consumers.
- *
  * The parent of this class is {@link PackageState}, which handles non-user state, exposing this
  * interface for per-user state.
  *
  * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-@Immutable.Ignore(reason = "Exposed through PackageState pending refactor")
+@Immutable
 public interface PackageUserState {
 
-    /** @hide */
+    /**
+     * @return whether the package is marked as installed
+     */
+    boolean isInstalled();
+
+    // Methods below this comment are not yet exposed as API
+
+    /**
+     * @hide
+     */
     @NonNull
     PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT;
 
     /**
      * Combination of {@link #getOverlayPaths()} and {@link #getSharedLibraryOverlayPaths()}
+     *
      * @hide
      */
+    @Immutable.Ignore
     @Nullable
     OverlayPaths getAllOverlayPaths();
 
     /**
      * Credential encrypted /data partition inode.
+     *
+     * @hide
      */
     long getCeDataInode();
 
     /**
      * Fully qualified class names of components explicitly disabled.
+     *
+     * @hide
      */
     @NonNull
     ArraySet<String> getDisabledComponents();
 
+    /**
+     * @hide
+     */
     @PackageManager.DistractionRestriction
     int getDistractionFlags();
 
     /**
      * Fully qualified class names of components explicitly enabled.
+     *
+     * @hide
      */
     @NonNull
     ArraySet<String> getEnabledComponents();
 
     /**
      * Retrieve the effective enabled state of the package itself.
+     *
+     * @hide
      */
     @PackageManager.EnabledState
     int getEnabledState();
 
     /**
+     * @hide
      * @see PackageManager#setHarmfulAppWarning(String, CharSequence)
      */
     @Nullable
     String getHarmfulAppWarning();
 
+    /**
+     * @hide
+     */
     @PackageManager.InstallReason
     int getInstallReason();
 
     /**
      * Tracks the last calling package to set a specific enabled state for the package.
+     *
+     * @hide
      */
     @Nullable
     String getLastDisableAppCaller();
 
-    /** @hide */
+    /**
+     * @hide
+     */
+    @Immutable.Ignore
     @Nullable
     OverlayPaths getOverlayPaths();
 
-    /** @hide */
+    /**
+     * @hide
+     */
+    @Immutable.Ignore
     @NonNull
     Map<String, OverlayPaths> getSharedLibraryOverlayPaths();
 
+    /**
+     * @hide
+     */
     @PackageManager.UninstallReason
     int getUninstallReason();
 
     /**
      * @return whether the given fully qualified class name is explicitly enabled
+     * @hide
      */
     boolean isComponentEnabled(@NonNull String componentName);
 
     /**
      * @return {@link #isComponentEnabled(String)} but for explicitly disabled
+     * @hide
      */
     boolean isComponentDisabled(@NonNull String componentName);
 
     /**
+     * @hide
      * @see PackageManager#setApplicationHiddenSettingAsUser(String, boolean, UserHandle)
      */
     boolean isHidden();
 
     /**
-     * @return whether the package is marked as installed for all users
-     */
-    boolean isInstalled();
-
-    /**
      * @return whether the package is marked as an ephemeral app, which restricts permissions,
      * features, visibility
+     * @hide
      */
     boolean isInstantApp();
 
     /**
      * @return whether the package has not been launched since being explicitly stopped
+     * @hide
      */
     boolean isNotLaunched();
 
     /**
      * @return whether the package has been stopped, which can occur if it's force-stopped, data
      * cleared, or just been installed
+     * @hide
      */
     boolean isStopped();
 
     /**
      * @return whether the package has been suspended, maybe by the device admin, disallowing its
      * launch
+     * @hide
      */
     boolean isSuspended();
 
     /**
      * @return whether the package was installed as a virtual preload, which may be done as part
      * of device infrastructure auto installation outside of the initial device image
+     * @hide
      */
     boolean isVirtualPreload();
 
     /**
      * The "package:type/entry" form of the theme resource ID previously set as the splash screen.
+     *
+     * @hide
      * @see android.window.SplashScreen#setSplashScreenTheme(int)
      * @see android.content.res.Resources#getResourceName(int)
      */
@@ -163,8 +203,10 @@
      * In epoch milliseconds. The timestamp of the first install of the app of the particular user
      * on the device, surviving past app updates. Different users might have a different first
      * install time.
-     *
+     * <p/>
      * This does not survive full removal of the app (i.e., uninstalls for all users).
+     *
+     * @hide
      */
     long getFirstInstallTime();
 }
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 daa4545..2d2e062 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -30,6 +30,7 @@
 import java.util.Collections;
 import java.util.Map;
 
+/** @hide */
 class PackageUserStateDefault implements PackageUserStateInternal {
 
     @Override
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 da22b17..a536f90 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -41,6 +41,7 @@
 import java.util.Map;
 import java.util.Objects;
 
+/** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
 @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
         "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"})
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index 96225c0..46cc830 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -29,6 +29,8 @@
  * Internal variant of {@link PackageUserState} that includes data not exposed as API. This is
  * still read-only and should be used inside system server code when possible over the
  * implementation.
+ *
+ * @hide
  */
 public interface PackageUserStateInternal extends PackageUserState, FrameworkPackageUserState {
 
diff --git a/services/core/java/com/android/server/pm/pkg/SharedLibrary.java b/services/core/java/com/android/server/pm/pkg/SharedLibrary.java
new file mode 100644
index 0000000..20f05f6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedLibrary.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.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
+import android.processor.immutability.Immutable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public interface SharedLibrary {
+
+    /**
+     * @see SharedLibraryInfo#getPath()
+     */
+    @Nullable
+    String getPath();
+
+    /**
+     * @see SharedLibraryInfo#getPackageName()
+     */
+    @Nullable
+    String getPackageName();
+
+    /**
+     * @see SharedLibraryInfo#getName()
+     */
+    @Nullable
+    String getName();
+
+    /**
+     * @see SharedLibraryInfo#getAllCodePaths()
+     */
+    @NonNull
+    List<String> getAllCodePaths();
+
+    /**
+     * @see SharedLibraryInfo#getLongVersion()
+     */
+    long getVersion();
+
+    /**
+     * @see SharedLibraryInfo#getType()
+     */
+    int getType();
+
+    /**
+     * @see SharedLibraryInfo#isNative()
+     */
+    boolean isNative();
+
+    /**
+     * @see SharedLibraryInfo#getDeclaringPackage()
+     */
+    @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS})
+    @NonNull
+    VersionedPackage getDeclaringPackage();
+
+    /**
+     * @see SharedLibraryInfo#getDependentPackages()
+     */
+    @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS})
+    @NonNull
+    List<VersionedPackage> getDependentPackages();
+
+    /**
+     * @see SharedLibraryInfo#getDependencies()
+     */
+    @NonNull
+    List<SharedLibrary> getDependencies();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java b/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java
new file mode 100644
index 0000000..2f1fe1a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedLibraryWrapper.java
@@ -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 com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+public class SharedLibraryWrapper implements SharedLibrary {
+
+    private final SharedLibraryInfo mInfo;
+
+    @Nullable
+    private List<SharedLibrary> cachedDependenciesList;
+
+    public SharedLibraryWrapper(@NonNull SharedLibraryInfo info) {
+        mInfo = info;
+    }
+
+    @NonNull
+    public SharedLibraryInfo getInfo() {
+        return mInfo;
+    }
+
+    @Override
+    public String getPath() {
+        return mInfo.getPath();
+    }
+
+    @Override
+    public String getPackageName() {
+        return mInfo.getPackageName();
+    }
+
+    @Override
+    public String getName() {
+        return mInfo.getName();
+    }
+
+    @Override
+    public List<String> getAllCodePaths() {
+        return Collections.unmodifiableList(mInfo.getAllCodePaths());
+    }
+
+    @Override
+    public long getVersion() {
+        return mInfo.getLongVersion();
+    }
+
+    @Override
+    public int getType() {
+        return mInfo.getType();
+    }
+
+    @Override
+    public boolean isNative() {
+        return mInfo.isNative();
+    }
+
+    @NonNull
+    @Override
+    public VersionedPackage getDeclaringPackage() {
+        return mInfo.getDeclaringPackage();
+    }
+
+    @NonNull
+    @Override
+    public List<VersionedPackage> getDependentPackages() {
+        return Collections.unmodifiableList(mInfo.getDependentPackages());
+    }
+
+    @NonNull
+    @Override
+    public List<SharedLibrary> getDependencies() {
+        if (cachedDependenciesList == null) {
+            var dependencies = mInfo.getDependencies();
+            if (dependencies == null) {
+                cachedDependenciesList = Collections.emptyList();
+            } else {
+                var list = new ArrayList<SharedLibrary>();
+                for (int index = 0; index < dependencies.size(); index++) {
+                    list.add(new SharedLibraryWrapper(dependencies.get(index)));
+                }
+                cachedDependenciesList = Collections.unmodifiableList(list);
+            }
+        }
+        return cachedDependenciesList;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
index 55c305c..063f577 100644
--- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
+++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
@@ -27,6 +27,7 @@
 
 import java.util.List;
 
+/** @hide */
 public interface SharedUserApi {
 
     @NonNull
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 1a46e20..2626bb4 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
@@ -147,7 +147,7 @@
 
     ParsingPackage setSharedUserId(String sharedUserId);
 
-    ParsingPackage setStaticSharedLibName(String staticSharedLibName);
+    ParsingPackage setStaticSharedLibraryName(String staticSharedLibName);
 
     ParsingPackage setTaskAffinity(String taskAffinity);
 
@@ -221,7 +221,7 @@
 
     ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
 
-    ParsingPackage setSdkLibName(String sdkLibName);
+    ParsingPackage setSdkLibraryName(String sdkLibName);
 
     ParsingPackage setSdkLibVersionMajor(int sdkLibVersionMajor);
 
@@ -458,7 +458,7 @@
     Boolean getResizeableActivity();
 
     @Nullable
-    String getSdkLibName();
+    String getSdkLibraryName();
 
     @NonNull
     List<ParsedService> getServices();
@@ -473,7 +473,7 @@
     String[] getSplitNames();
 
     @Nullable
-    String getStaticSharedLibName();
+    String getStaticSharedLibraryName();
 
     int getTargetSdkVersion();
 
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 14f787e..952adda 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
@@ -238,7 +238,6 @@
      * of required system property within the overlay tag.
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
-    public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     public static final int PARSE_APK_IN_APEX = 1 << 9;
 
     public static final int PARSE_CHATTY = 1 << 31;
@@ -257,7 +256,6 @@
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
             PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
-            PARSE_FRAMEWORK_RES_SPLITS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ParseFlags {}
@@ -307,7 +305,7 @@
                         isCoreApp);
             }
         });
-        var parseResult = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
+        var parseResult = parser.parsePackage(input, file, parseFlags);
         if (parseResult.isError()) {
             return input.error(parseResult);
         }
@@ -356,14 +354,9 @@
      * not check whether {@code packageFile} has changed since the last parse, it's up to callers to
      * do so.
      */
-    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
-            List<File> frameworkSplits) {
-        if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
-                && frameworkSplits.size() > 0
-                && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
-            return parseClusterPackage(input, packageFile, frameworkSplits, flags);
-        } else if (packageFile.isDirectory()) {
-            return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
+    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
+        if (packageFile.isDirectory()) {
+            return parseClusterPackage(input, packageFile,  flags);
         } else {
             return parseMonolithicPackage(input, packageFile, flags);
         }
@@ -375,28 +368,17 @@
      * identical package name and version codes, a single base APK, and unique
      * split names.
      * <p>
-     * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
-     * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
-     * even if they are in different folders. Note that this code path may have other behaviour
-     * differences.
-     * <p>
      * Note that this <em>does not</em> perform signature verification; that must be done separately
      * in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}.
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
-            List<File> frameworkSplits, int flags) {
-        // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
-        // pass the flags for framework splits
+            int flags) {
         int liteParseFlags = 0;
-        if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
-            liteParseFlags = flags;
-        }
         if ((flags & PARSE_APK_IN_APEX) != 0) {
             liteParseFlags |= PARSE_APK_IN_APEX;
         }
         final ParseResult<PackageLite> liteResult =
-                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
-                        liteParseFlags);
+                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
@@ -647,7 +629,7 @@
         final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
         try {
             final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,
-                    "coreApp",false);
+                    "coreApp", false);
             final ParsingPackage pkg = mCallback.startParsingPackage(
                     pkgName, apkPath, codePath, manifestArray, isCoreApp);
             final ParseResult<ParsingPackage> result =
@@ -2182,8 +2164,8 @@
             }
         }
 
-        if (TextUtils.isEmpty(pkg.getStaticSharedLibName()) && TextUtils.isEmpty(
-                pkg.getSdkLibName())) {
+        if (TextUtils.isEmpty(pkg.getStaticSharedLibraryName()) && TextUtils.isEmpty(
+                pkg.getSdkLibraryName())) {
             // Add a hidden app detail activity to normal apps which forwards user to App Details
             // page.
             ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
@@ -2373,12 +2355,12 @@
                         PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
                         "sharedUserId not allowed in SDK library"
                 );
-            } else if (pkg.getSdkLibName() != null) {
+            } else if (pkg.getSdkLibraryName() != null) {
                 return input.error("Multiple SDKs for package "
                         + pkg.getPackageName());
             }
 
-            return input.success(pkg.setSdkLibName(lname.intern())
+            return input.success(pkg.setSdkLibraryName(lname.intern())
                     .setSdkLibVersionMajor(versionMajor)
                     .setSdkLibrary(true));
         } finally {
@@ -2411,12 +2393,12 @@
                         PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
                         "sharedUserId not allowed in static shared library"
                 );
-            } else if (pkg.getStaticSharedLibName() != null) {
+            } else if (pkg.getStaticSharedLibraryName() != null) {
                 return input.error("Multiple static-shared libs for package "
                         + pkg.getPackageName());
             }
 
-            return input.success(pkg.setStaticSharedLibName(lname.intern())
+            return input.success(pkg.setStaticSharedLibraryName(lname.intern())
                     .setStaticSharedLibVersion(
                             PackageInfo.composeLongVersionCode(versionMajor, version))
                     .setStaticSharedLibrary(true));
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 9b7d19a..f8fcaff 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -26,7 +26,6 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
 import android.os.Environment;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -39,6 +38,7 @@
 import com.android.server.LocalServices;
 import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
 import com.android.server.policy.devicestate.config.DeviceStateConfig;
 import com.android.server.policy.devicestate.config.Flags;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 675819a..a0748b4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -121,7 +121,6 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -204,6 +203,7 @@
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -1662,7 +1662,7 @@
 
     private void toggleNotificationPanel() {
         IStatusBarService statusBarService = getStatusBarService();
-        if (statusBarService != null) {
+        if (isUserSetupComplete() && statusBarService != null) {
             try {
                 statusBarService.togglePanel();
             } catch (RemoteException e) {
@@ -2931,9 +2931,17 @@
                 }
                 return key_consumed;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
+                if (down) {
+                    mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
+                }
+                return key_consumed;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
+                if (down) {
+                    mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
+                }
+                return key_consumed;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
-                // TODO: Add logic to handle keyboard backlight controls (go/pk_backlight_control)
+                // TODO: Add logic
                 return key_consumed;
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -5510,6 +5518,7 @@
         switch (effectId) {
             case HapticFeedbackConstants.CONTEXT_CLICK:
             case HapticFeedbackConstants.GESTURE_END:
+            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
                 if (!mHapticTextHandleEnabled) {
@@ -5528,6 +5537,8 @@
             case HapticFeedbackConstants.EDGE_RELEASE:
             case HapticFeedbackConstants.CONFIRM:
             case HapticFeedbackConstants.GESTURE_START:
+            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584..69fb22c 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -20,18 +20,20 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.BatteryStats;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IWakeLockCallback;
 import android.os.Looper;
@@ -59,6 +61,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -137,7 +140,9 @@
     private final NotifierHandler mHandler;
     private final Executor mBackgroundExecutor;
     private final Intent mScreenOnIntent;
+    private final Bundle mScreenOnOptions;
     private final Intent mScreenOffIntent;
+    private final Bundle mScreenOffOptions;
 
     // True if the device should suspend when the screen is off due to proximity.
     private final boolean mSuspendWhenScreenOffDueToProximityConfig;
@@ -199,10 +204,14 @@
         mScreenOnIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter(
+                new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle();
         mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
         mScreenOffIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter(
+                new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle();
 
         mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
@@ -788,7 +797,8 @@
 
         if (mActivityManagerInternal.isSystemReady()) {
             mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
-                    mWakeUpBroadcastDone, mHandler, 0, null, null);
+                    AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler,
+                    0, null, null);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
             sendNextBroadcast();
@@ -811,7 +821,8 @@
 
         if (mActivityManagerInternal.isSystemReady()) {
             mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
-                    mGoToSleepBroadcastDone, mHandler, 0, null, null);
+                    AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler,
+                    0, null, null);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
             sendNextBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2d22b8f..f9352cb 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5316,7 +5316,7 @@
     private final class SuspendBlockerImpl implements SuspendBlocker {
         private static final String UNKNOWN_ID = "unknown";
         private final String mName;
-        private final String mTraceName;
+        private final int mNameHash;
         private int mReferenceCount;
 
         // Maps suspend blocker IDs to a list (LongArray) of open acquisitions for the suspend
@@ -5325,7 +5325,7 @@
 
         public SuspendBlockerImpl(String name) {
             mName = name;
-            mTraceName = "SuspendBlocker (" + name + ")";
+            mNameHash = mName.hashCode();
         }
 
         @Override
@@ -5336,7 +5336,8 @@
                             + "\" was finalized without being released!");
                     mReferenceCount = 0;
                     mNativeWrapper.nativeReleaseSuspendBlocker(mName);
-                    Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+                            "SuspendBlockers", mNameHash);
                 }
             } finally {
                 super.finalize();
@@ -5357,7 +5358,8 @@
                     if (DEBUG_SPEW) {
                         Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\".");
                     }
-                    Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                    Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_POWER,
+                            "SuspendBlockers", mName, mNameHash);
                     mNativeWrapper.nativeAcquireSuspendBlocker(mName);
                 }
             }
@@ -5378,7 +5380,10 @@
                         Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\".");
                     }
                     mNativeWrapper.nativeReleaseSuspendBlocker(mName);
-                    Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+                                "SuspendBlockers", mNameHash);
+                    }
                 } else if (mReferenceCount < 0) {
                     Slog.wtf(TAG, "Suspend blocker \"" + mName
                             + "\" was released without being acquired!", new Throwable());
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 c6128f9..6e93171 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -270,6 +270,8 @@
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+    private int[] mCpuPowerBracketMap;
+    private final CpuUsageDetails mCpuUsageDetails = new CpuUsageDetails();
 
     public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
         return mKernelMemoryStats;
@@ -478,7 +480,7 @@
     @GuardedBy("this")
     @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
     @VisibleForTesting
-    public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+    public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (!initKernelSingleUidTimeReaderLocked()) {
             return;
         }
@@ -488,12 +490,20 @@
         mNumSingleUidCpuTimeReads++;
 
         LongArrayMultiStateCounter onBatteryCounter =
-                u.getProcStateTimeCounter(timestampMs).getCounter();
+                u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
         LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+                u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
 
-        mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+        if (isUsageHistoryEnabled()) {
+            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                    getCpuTimeInFreqContainer();
+            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs,
+                    deltaContainer);
+            recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+        } else {
+            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
+        }
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
 
         if (u.mChildUids != null) {
             LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
@@ -504,14 +514,27 @@
                         u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
                 if (cpuTimeInFreqCounter != null) {
                     mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
-                            cpuTimeInFreqCounter, timestampMs, deltaContainer);
+                            cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
                     onBatteryCounter.addCounts(deltaContainer);
+                    if (isUsageHistoryEnabled()) {
+                        recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                    }
                     onBatteryScreenOffCounter.addCounts(deltaContainer);
                 }
             }
         }
     }
 
+    private void recordCpuUsage(int uid, LongArrayMultiStateCounter.LongArrayContainer cpuUsage,
+            long elapsedRealtimeMs, long uptimeMs) {
+        if (!cpuUsage.combineValues(mCpuUsageDetails.cpuUsageMs, mCpuPowerBracketMap)) {
+            return;
+        }
+
+        mCpuUsageDetails.uid = uid;
+        mHistory.recordCpuUsage(elapsedRealtimeMs, uptimeMs, mCpuUsageDetails);
+    }
+
     /**
      * Removes kernel CPU stats for removed UIDs, in the order they were added to the
      * mPendingRemovedUids queue.
@@ -557,16 +580,26 @@
                     continue;
                 }
 
-                final long timestampMs = mClock.elapsedRealtime();
+                final long elapsedRealtimeMs = mClock.elapsedRealtime();
+                final long uptimeMs = mClock.uptimeMillis();
                 final LongArrayMultiStateCounter onBatteryCounter =
-                        u.getProcStateTimeCounter(timestampMs).getCounter();
+                        u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
                 final LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                        u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+                        u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
 
                 if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
-                    mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter, timestampMs);
+                    if (isUsageHistoryEnabled()) {
+                        LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                                getCpuTimeInFreqContainer();
+                        mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+                                elapsedRealtimeMs, deltaContainer);
+                        recordCpuUsage(parentUid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                    } else {
+                        mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+                                elapsedRealtimeMs);
+                    }
                     mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
-                            timestampMs);
+                            elapsedRealtimeMs);
                 } else {
                     Uid.ChildUid childUid = u.getChildUid(uid);
                     if (childUid != null) {
@@ -574,9 +607,12 @@
                         if (counter != null) {
                             final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
                                     getCpuTimeInFreqContainer();
-                            mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+                            mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
                                     deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
+                            if (isUsageHistoryEnabled()) {
+                                recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                            }
                             onBatteryScreenOffCounter.addCounts(deltaContainer);
                         }
                     }
@@ -585,17 +621,6 @@
         }
     }
 
-    @VisibleForTesting
-    public static long[] addCpuTimes(long[] timesA, long[] timesB) {
-        if (timesA != null && timesB != null) {
-            for (int i = timesA.length - 1; i >= 0; --i) {
-                timesA[i] += timesB[i];
-            }
-            return timesA;
-        }
-        return timesA == null ? (timesB == null ? null : timesB) : timesA;
-    }
-
     @GuardedBy("this")
     private boolean initKernelSingleUidTimeReaderLocked() {
         if (mKernelSingleUidTimeReader == null) {
@@ -6813,20 +6838,31 @@
         synchronized (mModemNetworkLock) {
             if (displayTransport == TRANSPORT_CELLULAR) {
                 mModemIfaces = includeInStringArray(mModemIfaces, iface);
-                if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces);
+                if (DEBUG) {
+                    Slog.d(TAG, "Note mobile iface " + iface + ": "
+                            + Arrays.toString(mModemIfaces));
+                }
             } else {
                 mModemIfaces = excludeFromStringArray(mModemIfaces, iface);
-                if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mModemIfaces);
+                if (DEBUG) {
+                    Slog.d(TAG, "Note non-mobile iface " + iface + ": "
+                            + Arrays.toString(mModemIfaces));
+                }
             }
         }
 
         synchronized (mWifiNetworkLock) {
             if (displayTransport == TRANSPORT_WIFI) {
                 mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
-                if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
+                if (DEBUG) {
+                    Slog.d(TAG, "Note wifi iface " + iface + ": " + Arrays.toString(mWifiIfaces));
+                }
             } else {
                 mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
-                if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
+                if (DEBUG) {
+                    Slog.d(TAG, "Note non-wifi iface " + iface + ": "
+                            + Arrays.toString(mWifiIfaces));
+                }
             }
         }
     }
@@ -7411,8 +7447,10 @@
     @GuardedBy("this")
     public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs,
             long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) {
-        mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
-                measuredEnergyDetails);
+        if (isUsageHistoryEnabled()) {
+            mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
+                    measuredEnergyDetails);
+        }
     }
 
     @GuardedBy("this")
@@ -10274,7 +10312,7 @@
                 }
 
                 if (mBsi.trackPerProcStateCpuTimes()) {
-                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs);
 
                     LongArrayMultiStateCounter onBatteryCounter =
                             getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
@@ -10773,6 +10811,10 @@
         mBatteryLevel = 0;
     }
 
+    /**
+     * Injects a power profile.
+     */
+    @GuardedBy("this")
     public void setPowerProfileLocked(PowerProfile profile) {
         mPowerProfile = profile;
 
@@ -10789,6 +10831,27 @@
             firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
         }
 
+        // Initialize CPU power bracket map, which combines CPU states (cluster/freq pairs)
+        // into a small number of brackets
+        mCpuPowerBracketMap = new int[getCpuFreqCount()];
+        int index = 0;
+        int numCpuClusters = mPowerProfile.getNumCpuClusters();
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            int steps = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int step = 0; step < steps; step++) {
+                mCpuPowerBracketMap[index++] =
+                        mPowerProfile.getPowerBracketForCpuCore(cluster, step);
+            }
+        }
+
+        int cpuPowerBracketCount = mPowerProfile.getCpuPowerBracketCount();
+        mCpuUsageDetails.cpuBracketDescriptions = new String[cpuPowerBracketCount];
+        mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
+        for (int i = 0; i < cpuPowerBracketCount; i++) {
+            mCpuUsageDetails.cpuBracketDescriptions[i] =
+                    mPowerProfile.getCpuPowerBracketDescription(i);
+        }
+
         if (mEstimatedBatteryCapacityMah == -1) {
             // Initialize the estimated battery capacity to a known preset one.
             mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
@@ -12622,8 +12685,7 @@
     private void updateCpuMeasuredEnergyStatsLocked(@NonNull long[] clusterChargeUC,
             @NonNull CpuDeltaPowerAccumulator accumulator) {
         if (DEBUG_ENERGY) {
-            Slog.d(TAG,
-                    "Updating cpu cluster stats: " + clusterChargeUC.toString());
+            Slog.d(TAG, "Updating cpu cluster stats: " + Arrays.toString(clusterChargeUC));
         }
         if (mGlobalMeasuredEnergyStats == null) {
             return;
@@ -14620,11 +14682,16 @@
     }
 
     @GuardedBy("this")
-    public boolean trackPerProcStateCpuTimes() {
+    private boolean trackPerProcStateCpuTimes() {
         return mCpuUidFreqTimeReader.isFastCpuTimesReader();
     }
 
     @GuardedBy("this")
+    private boolean isUsageHistoryEnabled() {
+        return false;
+    }
+
+    @GuardedBy("this")
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
         registerUsbStateReceiver(context);
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 8b30995..d8e6c26 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -53,7 +53,7 @@
 
     private static class DataElement {
         private static final int LENGTH_FIELD_WIDTH = 4;
-        private static final int MAX_DATA_ELEMENT_SIZE = 1000;
+        private static final int MAX_DATA_ELEMENT_SIZE = 32768;
 
         private byte[] mData;
 
diff --git a/services/core/java/com/android/server/resources/OWNERS b/services/core/java/com/android/server/resources/OWNERS
new file mode 100644
index 0000000..7460a14
--- /dev/null
+++ b/services/core/java/com/android/server/resources/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 568761
+
+patb@google.com
+zyy@google.com
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
index cc27546..eec3a02 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerService.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.IResourcesManager;
+import android.content.res.ResourceTimer;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -55,7 +56,7 @@
 
     @Override
     public void onStart() {
-        // Intentionally left empty.
+        ResourceTimer.start();
     }
 
     private final IBinder mService = new IResourcesManager.Stub() {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index dc4bdaa..ce1157e 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.soundtrigger.ModelParameterRange;
@@ -33,6 +34,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.util.LatencyTracker;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -65,9 +68,12 @@
 public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
     private static final String TAG = "SoundTriggerMiddlewareLogging";
     private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
+    private final @NonNull Context mContext;
 
-    public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) {
+    public SoundTriggerMiddlewareLogging(@NonNull Context context,
+            @NonNull ISoundTriggerMiddlewareInternal delegate) {
         mDelegate = delegate;
+        mContext = context;
     }
 
     @Override
@@ -298,6 +304,7 @@
                     int captureSession)
                     throws RemoteException {
                 try {
+                    startKeyphraseEventLatencyTracking(event);
                     mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
                     logVoidReturn("onPhraseRecognition", modelHandle, event);
                 } catch (Exception e) {
@@ -347,6 +354,26 @@
                 logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
             }
 
+            /**
+             * Starts the latency tracking log for keyphrase hotword invocation.
+             * The measurement covers from when the SoundTrigger HAL emits an event to when the
+             * {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
+             */
+            private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) {
+                String latencyTrackerTag = null;
+                if (event.phraseExtras.length > 0) {
+                    latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id;
+                }
+                LatencyTracker latencyTracker = LatencyTracker.getInstance(mContext);
+                // To avoid adding cancel to all of the different failure modes between here and
+                // showing the system UI, we defensively cancel once.
+                // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel
+                // here if any error occurs.
+                latencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+                latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION,
+                        latencyTrackerTag);
+            }
+
             @Override
             public IBinder asBinder() {
                 return mCallbackDelegate.asBinder();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 1995e54..807ed14 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,14 +20,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseSoundModel;
-import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.SoundModel;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.PermissionUtil;
 import android.media.permission.SafeCloseable;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -226,12 +226,13 @@
             HalFactory[] factories = new HalFactory[]{new DefaultHalFactory()};
 
             publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
-                    new SoundTriggerMiddlewareService(new SoundTriggerMiddlewareLogging(
-                            new SoundTriggerMiddlewarePermission(
-                                    new SoundTriggerMiddlewareValidation(
-                                            new SoundTriggerMiddlewareImpl(factories,
-                                                    new AudioSessionProviderImpl())),
-                                    getContext())), getContext()));
+                    new SoundTriggerMiddlewareService(
+                            new SoundTriggerMiddlewareLogging(getContext(),
+                                new SoundTriggerMiddlewarePermission(
+                                        new SoundTriggerMiddlewareValidation(
+                                                new SoundTriggerMiddlewareImpl(factories,
+                                                        new AudioSessionProviderImpl())),
+                                        getContext())), getContext()));
         }
     }
 }
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
index 372bcc6..d9a4266 100644
--- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
@@ -46,6 +46,7 @@
 
     private final boolean mAutoDetectionSupported;
     private final int mSystemClockUpdateThresholdMillis;
+    private final int mSystemClockConfidenceThresholdMillis;
     private final Instant mAutoSuggestionLowerBound;
     private final Instant mManualSuggestionLowerBound;
     private final Instant mSuggestionUpperBound;
@@ -57,6 +58,8 @@
     private ConfigurationInternal(Builder builder) {
         mAutoDetectionSupported = builder.mAutoDetectionSupported;
         mSystemClockUpdateThresholdMillis = builder.mSystemClockUpdateThresholdMillis;
+        mSystemClockConfidenceThresholdMillis =
+                builder.mSystemClockConfidenceThresholdMillis;
         mAutoSuggestionLowerBound = Objects.requireNonNull(builder.mAutoSuggestionLowerBound);
         mManualSuggestionLowerBound = Objects.requireNonNull(builder.mManualSuggestionLowerBound);
         mSuggestionUpperBound = Objects.requireNonNull(builder.mSuggestionUpperBound);
@@ -82,6 +85,17 @@
     }
 
     /**
+     * Return the absolute threshold for Unix epoch time comparison at/below which the system clock
+     * confidence can be said to be "close enough", e.g. if the detector receives a high-confidence
+     * time and the current system clock is +/- this value from that time and the current confidence
+     * in the time is low, then the device's confidence in the current system clock time can be
+     * upgraded.
+     */
+    public int getSystemClockConfidenceThresholdMillis() {
+        return mSystemClockConfidenceThresholdMillis;
+    }
+
+    /**
      * Returns the lower bound for valid automatic time suggestions. It is guaranteed to be in the
      * past, i.e. it is unrelated to the current system clock time.
      * It holds no other meaning; it could be related to when the device system image was built,
@@ -180,7 +194,7 @@
         } else {
             suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
         }
-        builder.setSuggestManualTimeCapability(suggestManualTimeZoneCapability);
+        builder.setSetManualTimeCapability(suggestManualTimeZoneCapability);
 
         return builder.build();
     }
@@ -242,6 +256,8 @@
         return "ConfigurationInternal{"
                 + "mAutoDetectionSupported=" + mAutoDetectionSupported
                 + ", mSystemClockUpdateThresholdMillis=" + mSystemClockUpdateThresholdMillis
+                + ", mSystemClockConfidenceThresholdMillis="
+                + mSystemClockConfidenceThresholdMillis
                 + ", mAutoSuggestionLowerBound=" + mAutoSuggestionLowerBound
                 + "(" + mAutoSuggestionLowerBound.toEpochMilli() + ")"
                 + ", mManualSuggestionLowerBound=" + mManualSuggestionLowerBound
@@ -258,6 +274,7 @@
     static final class Builder {
         private boolean mAutoDetectionSupported;
         private int mSystemClockUpdateThresholdMillis;
+        private int mSystemClockConfidenceThresholdMillis;
         @NonNull private Instant mAutoSuggestionLowerBound;
         @NonNull private Instant mManualSuggestionLowerBound;
         @NonNull private Instant mSuggestionUpperBound;
@@ -286,68 +303,55 @@
             this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
         }
 
-        /**
-         * Sets whether the user is allowed to configure time settings on this device.
-         */
+        /** See {@link ConfigurationInternal#isUserConfigAllowed()}. */
         Builder setUserConfigAllowed(boolean userConfigAllowed) {
             mUserConfigAllowed = userConfigAllowed;
             return this;
         }
 
-        /**
-         * Sets whether automatic time detection is supported on this device.
-         */
+        /** See {@link ConfigurationInternal#isAutoDetectionSupported()}. */
         public Builder setAutoDetectionSupported(boolean supported) {
             mAutoDetectionSupported = supported;
             return this;
         }
 
-        /**
-         * Sets the absolute threshold below which the system clock need not be updated. i.e. if
-         * setting the system clock would adjust it by less than this (either backwards or forwards)
-         * then it need not be set.
-         */
+        /** See {@link ConfigurationInternal#getSystemClockUpdateThresholdMillis()}. */
         public Builder setSystemClockUpdateThresholdMillis(int systemClockUpdateThresholdMillis) {
             mSystemClockUpdateThresholdMillis = systemClockUpdateThresholdMillis;
             return this;
         }
 
-        /**
-         * Sets the lower bound for valid automatic time suggestions.
-         */
+        /** See {@link ConfigurationInternal#getSystemClockConfidenceThresholdMillis()}. */
+        public Builder setSystemClockConfidenceThresholdMillis(int thresholdMillis) {
+            mSystemClockConfidenceThresholdMillis = thresholdMillis;
+            return this;
+        }
+
+        /** See {@link ConfigurationInternal#getAutoSuggestionLowerBound()}. */
         public Builder setAutoSuggestionLowerBound(@NonNull Instant autoSuggestionLowerBound) {
             mAutoSuggestionLowerBound = Objects.requireNonNull(autoSuggestionLowerBound);
             return this;
         }
 
-        /**
-         * Sets the lower bound for valid manual time suggestions.
-         */
+        /** See {@link ConfigurationInternal#getManualSuggestionLowerBound()}. */
         public Builder setManualSuggestionLowerBound(@NonNull Instant manualSuggestionLowerBound) {
             mManualSuggestionLowerBound = Objects.requireNonNull(manualSuggestionLowerBound);
             return this;
         }
 
-        /**
-         * Sets the upper bound for valid time suggestions (manual and automatic).
-         */
+        /** See {@link ConfigurationInternal#getSuggestionUpperBound()}. */
         public Builder setSuggestionUpperBound(@NonNull Instant suggestionUpperBound) {
             mSuggestionUpperBound = Objects.requireNonNull(suggestionUpperBound);
             return this;
         }
 
-        /**
-         * Sets the order to look at time suggestions when automatically detecting time.
-         * See {@code #ORIGIN_} constants
-         */
+        /** See {@link ConfigurationInternal#getAutoOriginPriorities()}. */
         public Builder setOriginPriorities(@NonNull @Origin int... originPriorities) {
             mOriginPriorities = Objects.requireNonNull(originPriorities);
             return this;
         }
 
-        /**
-         * Sets the value of the automatic time detection enabled setting for this device.
-         */
+        /** See {@link ConfigurationInternal#getAutoDetectionEnabledSetting()}. */
         Builder setAutoDetectionEnabledSetting(boolean autoDetectionEnabledSetting) {
             mAutoDetectionEnabledSetting = autoDetectionEnabledSetting;
             return this;
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 3e02b46..4972412 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -16,16 +16,21 @@
 
 package com.android.server.timedetector;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
-import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
 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.ConfigurationChangeListener;
 
+import java.io.PrintWriter;
 import java.util.Objects;
 
 /**
@@ -38,7 +43,7 @@
     @NonNull private final Handler mHandler;
     @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final PowerManager.WakeLock mWakeLock;
-    @NonNull private final AlarmManager mAlarmManager;
+    @NonNull private final AlarmManagerInternal mAlarmManagerInternal;
 
     EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull ServiceConfigAccessor serviceConfigAccessor) {
@@ -49,7 +54,8 @@
         mWakeLock = Objects.requireNonNull(
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG));
 
-        mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class));
+        mAlarmManagerInternal = Objects.requireNonNull(
+                LocalServices.getService(AlarmManagerInternal.class));
     }
 
     @Override
@@ -84,9 +90,22 @@
     }
 
     @Override
-    public void setSystemClock(long newTimeMillis) {
+    public @TimeConfidence int systemClockConfidence() {
+        return SystemClockTime.getTimeConfidence();
+    }
+
+    @Override
+    public void setSystemClock(
+            @CurrentTimeMillisLong long newTimeMillis, @TimeConfidence int confidence,
+            @NonNull String logMsg) {
         checkWakeLockHeld();
-        mAlarmManager.setTime(newTimeMillis);
+        mAlarmManagerInternal.setTime(newTimeMillis, confidence, logMsg);
+    }
+
+    @Override
+    public void setSystemClockConfidence(@TimeConfidence int confidence, @NonNull String logMsg) {
+        checkWakeLockHeld();
+        SystemClockTime.setConfidence(confidence, logMsg);
     }
 
     @Override
@@ -100,4 +119,13 @@
             Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " not held");
         }
     }
-}
+
+    @Override
+    public void addDebugLogEntry(@NonNull String logMsg) {
+        SystemClockTime.addDebugLogEntry(logMsg);
+    }
+
+    @Override
+    public void dumpDebugLog(@NonNull PrintWriter printWriter) {
+        SystemClockTime.dump(printWriter);
+    }}
diff --git a/services/core/java/com/android/server/timedetector/GnssTimeSuggestion.java b/services/core/java/com/android/server/timedetector/GnssTimeSuggestion.java
index 3349975..a7b1e23 100644
--- a/services/core/java/com/android/server/timedetector/GnssTimeSuggestion.java
+++ b/services/core/java/com/android/server/timedetector/GnssTimeSuggestion.java
@@ -17,9 +17,9 @@
 package com.android.server.timedetector;
 
 import android.annotation.NonNull;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.TimeSuggestionHelper;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -34,7 +34,7 @@
 
     @NonNull private final TimeSuggestionHelper mTimeSuggestionHelper;
 
-    public GnssTimeSuggestion(@NonNull TimestampedValue<Long> unixEpochTime) {
+    public GnssTimeSuggestion(@NonNull UnixEpochTime unixEpochTime) {
         mTimeSuggestionHelper = new TimeSuggestionHelper(GnssTimeSuggestion.class, unixEpochTime);
     }
 
@@ -43,7 +43,7 @@
     }
 
     @NonNull
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mTimeSuggestionHelper.getUnixEpochTime();
     }
 
diff --git a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
index 421f7ce..fef7148 100644
--- a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.AlarmManager;
+import android.app.time.UnixEpochTime;
 import android.content.Context;
 import android.location.LocationListener;
 import android.location.LocationManager;
@@ -31,7 +32,6 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -122,7 +122,7 @@
     @GuardedBy("mLock") @Nullable private AlarmManager.OnAlarmListener mAlarmListener;
     @GuardedBy("mLock") @Nullable private LocationListener mLocationListener;
 
-    @Nullable private volatile TimestampedValue<Long> mLastSuggestedGnssTime;
+    @Nullable private volatile UnixEpochTime mLastSuggestedGnssTime;
 
     @VisibleForTesting
     GnssTimeUpdateService(@NonNull Context context, @NonNull AlarmManager alarmManager,
@@ -263,8 +263,7 @@
         long gnssUnixEpochTimeMillis = locationTime.getUnixEpochTimeMillis();
         long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
 
-        TimestampedValue<Long> timeSignal =
-                new TimestampedValue<>(elapsedRealtimeMs, gnssUnixEpochTimeMillis);
+        UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMs, gnssUnixEpochTimeMillis);
         mLastSuggestedGnssTime = timeSignal;
 
         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeSuggestion.java b/services/core/java/com/android/server/timedetector/NetworkTimeSuggestion.java
index f62c7ed..71c5d428 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeSuggestion.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeSuggestion.java
@@ -18,8 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -48,7 +48,7 @@
  */
 public final class NetworkTimeSuggestion {
 
-    @NonNull private final TimestampedValue<Long> mUnixEpochTime;
+    @NonNull private final UnixEpochTime mUnixEpochTime;
     private final int mUncertaintyMillis;
     @Nullable private ArrayList<String> mDebugInfo;
 
@@ -57,8 +57,7 @@
      *
      * <p>See {@link NetworkTimeSuggestion} for property details.
      */
-    public NetworkTimeSuggestion(
-            @NonNull TimestampedValue<Long> unixEpochTime, int uncertaintyMillis) {
+    public NetworkTimeSuggestion(@NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) {
         mUnixEpochTime = Objects.requireNonNull(unixEpochTime);
         if (uncertaintyMillis < 0) {
             throw new IllegalArgumentException("uncertaintyMillis < 0");
@@ -68,7 +67,7 @@
 
     /** See {@link NetworkTimeSuggestion} for property details. */
     @NonNull
-    public TimestampedValue<Long> getUnixEpochTime() {
+    public UnixEpochTime getUnixEpochTime() {
         return mUnixEpochTime;
     }
 
@@ -126,14 +125,15 @@
     /** Parses command line args to create a {@link NetworkTimeSuggestion}. */
     public static NetworkTimeSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
             throws IllegalArgumentException {
-        Long referenceTimeMillis = null;
+        Long elapsedRealtimeMillis = null;
         Long unixEpochTimeMillis = null;
         Integer uncertaintyMillis = null;
         String opt;
         while ((opt = cmd.getNextArg()) != null) {
             switch (opt) {
-                case "--reference_time": {
-                    referenceTimeMillis = Long.parseLong(cmd.getNextArgRequired());
+                case "--reference_time":
+                case "--elapsed_realtime": {
+                    elapsedRealtimeMillis = Long.parseLong(cmd.getNextArgRequired());
                     break;
                 }
                 case "--unix_epoch_time": {
@@ -150,8 +150,8 @@
             }
         }
 
-        if (referenceTimeMillis == null) {
-            throw new IllegalArgumentException("No referenceTimeMillis specified.");
+        if (elapsedRealtimeMillis == null) {
+            throw new IllegalArgumentException("No elapsedRealtimeMillis specified.");
         }
         if (unixEpochTimeMillis == null) {
             throw new IllegalArgumentException("No unixEpochTimeMillis specified.");
@@ -160,8 +160,7 @@
             throw new IllegalArgumentException("No uncertaintyMillis specified.");
         }
 
-        TimestampedValue<Long> timeSignal =
-                new TimestampedValue<>(referenceTimeMillis, unixEpochTimeMillis);
+        UnixEpochTime timeSignal = new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
         NetworkTimeSuggestion networkTimeSuggestion =
                 new NetworkTimeSuggestion(timeSignal, uncertaintyMillis);
         networkTimeSuggestion.addDebugInfo("Command line injection");
@@ -171,8 +170,8 @@
     /** Prints the command line args needed to create a {@link NetworkTimeSuggestion}. */
     public static void printCommandLineOpts(PrintWriter pw) {
         pw.printf("%s suggestion options:\n", "Network");
-        pw.println("  --reference_time <elapsed realtime millis> - the elapsed realtime millis when"
-                + " unix epoch time was read");
+        pw.println("  --elapsed_realtime <elapsed realtime millis> - the elapsed realtime millis"
+                + " when unix epoch time was read");
         pw.println("  --unix_epoch_time <Unix epoch time millis>");
         pw.println("  --uncertainty_millis <Uncertainty millis> - a positive error bound (+/-)"
                 + " estimate for unix epoch time");
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index 59c118d..4803011 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 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;
@@ -38,7 +39,6 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.util.LocalLog;
 import android.util.Log;
@@ -278,7 +278,7 @@
     /** Suggests the time to the time detector. It may choose use it to set the system clock. */
     private void makeNetworkTimeSuggestion(
             @NonNull TimeResult ntpResult, @NonNull String debugInfo) {
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+        UnixEpochTime timeSignal = new UnixEpochTime(
                 ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
         NetworkTimeSuggestion timeSuggestion =
                 new NetworkTimeSuggestion(timeSignal, ntpResult.getUncertaintyMillis());
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 888304a..84013a7 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -68,6 +68,15 @@
     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
+     * that time and the confidence in the time is low, then the device's confidence in the current
+     * system clock time can be upgraded. This needs to be an amount users would consider
+     * "close enough".
+     */
+    private static final int SYSTEM_CLOCK_CONFIRMATION_THRESHOLD_MILLIS = 1000;
+
+    /**
      * By default telephony and network only suggestions are accepted and telephony takes
      * precedence over network.
      */
@@ -236,6 +245,8 @@
                 .setAutoDetectionSupported(isAutoDetectionSupported())
                 .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
                 .setSystemClockUpdateThresholdMillis(getSystemClockUpdateThresholdMillis())
+                .setSystemClockConfidenceThresholdMillis(
+                        getSystemClockConfidenceUpgradeThresholdMillis())
                 .setAutoSuggestionLowerBound(getAutoSuggestionLowerBound())
                 .setManualSuggestionLowerBound(timeDetectorHelper.getManualSuggestionLowerBound())
                 .setSuggestionUpperBound(timeDetectorHelper.getSuggestionUpperBound())
@@ -285,6 +296,10 @@
         return mSystemClockUpdateThresholdMillis;
     }
 
+    private int getSystemClockConfidenceUpgradeThresholdMillis() {
+        return SYSTEM_CLOCK_CONFIRMATION_THRESHOLD_MILLIS;
+    }
+
     @NonNull
     private Instant getAutoSuggestionLowerBound() {
         return mServerFlags.getOptionalInstant(KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE)
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 5c47abf..39672b8 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -24,6 +24,8 @@
 import android.app.time.ITimeDetectorListener;
 import android.app.time.TimeCapabilitiesAndConfig;
 import android.app.time.TimeConfiguration;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
@@ -86,8 +88,10 @@
                     new TimeDetectorInternalImpl(context, handler, timeDetectorStrategy);
             publishLocalService(TimeDetectorInternal.class, internal);
 
+            CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeDetectorService service = new TimeDetectorService(
-                    context, handler, serviceConfigAccessor, timeDetectorStrategy);
+                    context, handler, callerIdentityInjector, serviceConfigAccessor,
+                    timeDetectorStrategy, NtpTrustedTime.getInstance(context));
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
@@ -112,23 +116,15 @@
 
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
-            @NonNull TimeDetectorStrategy timeDetectorStrategy) {
-        this(context, handler, serviceConfigAccessor, timeDetectorStrategy,
-                CallerIdentityInjector.REAL, NtpTrustedTime.getInstance(context));
-    }
-
-    @VisibleForTesting
-    public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
+            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeDetectorStrategy timeDetectorStrategy,
-            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull NtpTrustedTime ntpTrustedTime) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
+        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
-        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime);
 
         // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
@@ -272,6 +268,58 @@
     }
 
     @Override
+    public TimeState getTimeState() {
+        enforceManageTimeDetectorPermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mTimeDetectorStrategy.getTimeState();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    void setTimeState(@NonNull TimeState timeState) {
+        enforceManageTimeDetectorPermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mTimeDetectorStrategy.setTimeState(timeState);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean confirmTime(@NonNull UnixEpochTime time) {
+        enforceManageTimeDetectorPermission();
+        Objects.requireNonNull(time);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mTimeDetectorStrategy.confirmTime(time);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean setManualTime(@NonNull ManualTimeSuggestion timeSignal) {
+        enforceManageTimeDetectorPermission();
+        Objects.requireNonNull(timeSignal);
+
+        // This calls suggestManualTime() as the logic is identical, it only differs in the
+        // permission required, which is handled on the line above.
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mTimeDetectorStrategy.suggestManualTime(userId, timeSignal);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
     public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) {
         enforceSuggestTelephonyTimePermission();
         Objects.requireNonNull(timeSignal);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index d306d10..990c00f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -15,9 +15,12 @@
  */
 package com.android.server.timedetector;
 
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME;
@@ -30,6 +33,8 @@
 
 import android.app.time.ExternalTimeSuggestion;
 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.ShellCommand;
@@ -69,6 +74,12 @@
                 return runSuggestGnssTime();
             case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
                 return runSuggestExternalTime();
+            case SHELL_COMMAND_GET_TIME_STATE:
+                return runGetTimeState();
+            case SHELL_COMMAND_SET_TIME_STATE:
+                return runSetTimeState();
+            case SHELL_COMMAND_CONFIRM_TIME:
+                return runConfirmTime();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -140,6 +151,24 @@
         }
     }
 
+    private int runGetTimeState() {
+        TimeState timeState = mInterface.getTimeState();
+        getOutPrintWriter().println(timeState);
+        return 0;
+    }
+
+    private int runSetTimeState() {
+        TimeState timeState = TimeState.parseCommandLineArgs(this);
+        mInterface.setTimeState(timeState);
+        return 0;
+    }
+
+    private int runConfirmTime() {
+        UnixEpochTime unixEpochTime = UnixEpochTime.parseCommandLineArgs(this);
+        getOutPrintWriter().println(mInterface.confirmTime(unixEpochTime));
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -161,6 +190,12 @@
         pw.printf("    Suggests a time as if via the \"gnss\" origin.\n");
         pw.printf("  %s <external suggestion opts>\n", SHELL_COMMAND_SUGGEST_EXTERNAL_TIME);
         pw.printf("    Suggests a time as if via the \"external\" origin.\n");
+        pw.printf("  %s\n", SHELL_COMMAND_GET_TIME_STATE);
+        pw.printf("    Returns the current time setting state.\n");
+        pw.printf("  %s <time state options>\n", SHELL_COMMAND_SET_TIME_STATE);
+        pw.printf("    Sets the current time state for tests.\n");
+        pw.printf("  %s <unix epoch time options>\n", SHELL_COMMAND_CONFIRM_TIME);
+        pw.printf("    Tries to confirms the time, raising the confidence.\n");
         pw.println();
         ManualTimeSuggestion.printCommandLineOpts(pw);
         pw.println();
@@ -172,6 +207,10 @@
         pw.println();
         ExternalTimeSuggestion.printCommandLineOpts(pw);
         pw.println();
+        TimeState.printCommandLineOpts(pw);
+        pw.println();
+        UnixEpochTime.printCommandLineOpts(pw);
+        pw.println();
         pw.printf("This service is also affected by the following device_config flags in the"
                 + " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
         pw.printf("  %s\n", KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 141cdcf..bc86ed0 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -20,9 +20,10 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+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.internal.util.Preconditions;
@@ -65,6 +66,25 @@
     /** Used when a time value originated from an externally specified signal. */
     @Origin int ORIGIN_EXTERNAL = 5;
 
+    /** Returns a snapshot of the system clock's state. See {@link TimeState} for details. */
+    @NonNull
+    TimeState getTimeState();
+
+    /**
+     * Sets the system time state. See {@link TimeState} for details. Intended for use during
+     * testing to force the device's state, this bypasses the time detection logic.
+     */
+    void setTimeState(@NonNull TimeState timeState);
+
+    /**
+     * Signals that a user has confirmed the supplied time. If the {@code confirmationTime},
+     * adjusted for elapsed time since it was created (expected to be with {@link
+     * #getTimeState()}), is very close to the clock's current state, then this can be used to
+     * raise the system's confidence in that time. Returns {@code true} if confirmation was
+     * successful (i.e. the time matched), {@code false} otherwise.
+     */
+    boolean confirmTime(@NonNull UnixEpochTime confirmationTime);
+
     /** Processes the suggested time from telephony sources. */
     void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
 
@@ -88,18 +108,10 @@
     // Utility methods below are to be moved to a better home when one becomes more obvious.
 
     /**
-     * Adjusts the supplied time value by applying the difference between the reference time
-     * supplied and the reference time associated with the time.
-     */
-    static long getTimeAt(@NonNull TimestampedValue<Long> timeValue, long referenceClockMillisNow) {
-        return (referenceClockMillisNow - timeValue.getReferenceTimeMillis())
-                + timeValue.getValue();
-    }
-
-    /**
      * Converts one of the {@code ORIGIN_} constants to a human readable string suitable for config
      * and debug usage. Throws an {@link IllegalArgumentException} if the value is unrecognized.
      */
+    @NonNull
     static String originToString(@Origin int origin) {
         switch (origin) {
             case ORIGIN_MANUAL:
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 547cf9d..c3f05cc 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.timedetector;
 
+import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
+import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
 import static com.android.server.timedetector.TimeDetectorStrategy.originToString;
 
 import android.annotation.CurrentTimeMillisLong;
@@ -23,32 +25,32 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.AlarmManager;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.content.Context;
 import android.os.Handler;
-import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
-import android.util.LocalLog;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemClockTime;
+import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timezonedetector.ArrayMapWithHistory;
 import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ReferenceWithHistory;
 
+import java.io.PrintWriter;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Objects;
 
 /**
- * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
- * {@link AlarmManager}. When there are multiple telephony sources, the one with the lowest ID is
- * used unless the data becomes too stale.
+ * The real implementation of {@link TimeDetectorStrategy}.
  *
  * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
  */
@@ -84,13 +86,6 @@
      */
     private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
 
-    /**
-     * A log that records the decisions / decision metadata that affected the device's system clock
-     * time. This is logged in bug reports to assist with debugging issues with detection.
-     */
-    @NonNull
-    private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
     @NonNull
     private final Environment mEnvironment;
 
@@ -103,7 +98,7 @@
     // going through this strategy code.
     @GuardedBy("this")
     @Nullable
-    private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
+    private UnixEpochTime mLastAutoSystemClockTimeSet;
 
     /**
      * A mapping from slotIndex to a time suggestion. We typically expect one or two mappings:
@@ -157,11 +152,30 @@
         @CurrentTimeMillisLong
         long systemClockMillis();
 
-        /** Sets the device system clock. The WakeLock must be held. */
-        void setSystemClock(@CurrentTimeMillisLong long newTimeMillis);
+        /** Returns the system clock confidence value. */
+        @TimeConfidence int systemClockConfidence();
+
+        /** Sets the device system clock and confidence. The WakeLock must be held. */
+        void setSystemClock(
+                @CurrentTimeMillisLong long newTimeMillis, @TimeConfidence int confidence,
+                @NonNull String logMsg);
+
+        /** Sets the device system clock confidence. The WakeLock must be held. */
+        void setSystemClockConfidence(@TimeConfidence int confidence, @NonNull String logMsg);
 
         /** 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}.
+         */
+        void dumpDebugLog(PrintWriter printWriter);
     }
 
     static TimeDetectorStrategy create(
@@ -194,7 +208,7 @@
         }
         Objects.requireNonNull(suggestion);
 
-        final TimestampedValue<Long> newUnixEpochTime = suggestion.getUnixEpochTime();
+        final UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
 
         if (!validateAutoSuggestionTime(newUnixEpochTime, suggestion)) {
             return;
@@ -216,7 +230,7 @@
         }
         Objects.requireNonNull(suggestion);
 
-        final TimestampedValue<Long> newUnixEpochTime = suggestion.getUnixEpochTime();
+        final UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
 
         if (!validateAutoSuggestionTime(newUnixEpochTime, suggestion)) {
             return;
@@ -243,14 +257,14 @@
 
         Objects.requireNonNull(suggestion);
 
-        final TimestampedValue<Long> newUnixEpochTime = suggestion.getUnixEpochTime();
+        final UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
 
         if (!validateManualSuggestionTime(newUnixEpochTime, suggestion)) {
             return false;
         }
 
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
-        return setSystemClockIfRequired(ORIGIN_MANUAL, newUnixEpochTime, cause);
+        return setSystemClockAndConfidenceIfRequired(ORIGIN_MANUAL, newUnixEpochTime, cause);
     }
 
     @Override
@@ -286,6 +300,71 @@
     }
 
     @Override
+    @NonNull
+    public synchronized TimeState getTimeState() {
+        boolean userShouldConfirmTime = mEnvironment.systemClockConfidence() < TIME_CONFIDENCE_HIGH;
+        UnixEpochTime unixEpochTime = new UnixEpochTime(
+                mEnvironment.elapsedRealtimeMillis(), mEnvironment.systemClockMillis());
+        return new TimeState(unixEpochTime, userShouldConfirmTime);
+    }
+
+    @Override
+    public synchronized void setTimeState(@NonNull TimeState timeState) {
+        Objects.requireNonNull(timeState);
+
+        @TimeConfidence int confidence = timeState.getUserShouldConfirmTime()
+                ? TIME_CONFIDENCE_LOW : TIME_CONFIDENCE_HIGH;
+        mEnvironment.acquireWakeLock();
+        try {
+            // The origin is a lie but this method is only used for command line / manual testing
+            // to force the device into a specific state.
+            @Origin int origin = ORIGIN_MANUAL;
+            UnixEpochTime unixEpochTime = timeState.getUnixEpochTime();
+            setSystemClockAndConfidenceUnderWakeLock(
+                    origin, unixEpochTime, confidence, "setTimeZoneState()");
+        } finally {
+            mEnvironment.releaseWakeLock();
+        }
+    }
+
+    @Override
+    public synchronized boolean confirmTime(@NonNull UnixEpochTime confirmationTime) {
+        Objects.requireNonNull(confirmationTime);
+
+        // All system clock calculation take place under a wake lock.
+        mEnvironment.acquireWakeLock();
+        try {
+            // Check if the specified time matches the current system clock time (closely
+            // enough) to raise the confidence.
+            long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+            long currentSystemClockMillis = mEnvironment.systemClockMillis();
+            boolean timeConfirmed = isTimeWithinConfidenceThreshold(
+                    confirmationTime, currentElapsedRealtimeMillis, currentSystemClockMillis);
+            if (timeConfirmed) {
+                @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
+                @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
+                boolean confidenceUpgradeRequired = currentTimeConfidence < newTimeConfidence;
+                if (confidenceUpgradeRequired) {
+                    String logMsg = "Confirm system clock time."
+                            + " confirmationTime=" + confirmationTime
+                            + " newTimeConfidence=" + newTimeConfidence
+                            + " currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis
+                            + " currentSystemClockMillis=" + currentSystemClockMillis
+                            + " (old) currentTimeConfidence=" + currentTimeConfidence;
+                    if (DBG) {
+                        Slog.d(LOG_TAG, logMsg);
+                    }
+
+                    mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
+                }
+            }
+            return timeConfirmed;
+        } finally {
+            mEnvironment.releaseWakeLock();
+        }
+    }
+
+    @Override
     public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
         // Empty time suggestion means that telephony network connectivity has been lost.
         // The passage of time is relentless, and we don't expect our users to use a time machine,
@@ -318,7 +397,7 @@
         String logMsg = "handleConfigurationInternalChanged:"
                 + " oldConfiguration=" + mCurrentConfigurationInternal
                 + ", newConfiguration=" + currentUserConfig;
-        logTimeDetectorChange(logMsg);
+        addDebugLogEntry(logMsg);
         mCurrentConfigurationInternal = currentUserConfig;
 
         boolean autoDetectionEnabled =
@@ -335,11 +414,11 @@
         }
     }
 
-    private void logTimeDetectorChange(@NonNull String logMsg) {
+    private void addDebugLogEntry(@NonNull String logMsg) {
         if (DBG) {
             Slog.d(LOG_TAG, logMsg);
         }
-        mTimeChangesLog.log(logMsg);
+        mEnvironment.addDebugLogEntry(logMsg);
     }
 
     @Override
@@ -356,10 +435,11 @@
         long systemClockMillis = mEnvironment.systemClockMillis();
         ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n",
                 Instant.ofEpochMilli(systemClockMillis), systemClockMillis);
+        ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence());
 
         ipw.println("Time change log:");
         ipw.increaseIndent(); // level 2
-        mTimeChangesLog.dump(ipw);
+        SystemClockTime.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
         ipw.println("Telephony suggestion history:");
@@ -386,16 +466,14 @@
     }
 
     @GuardedBy("this")
-    private boolean storeTelephonySuggestion(
-            @NonNull TelephonyTimeSuggestion suggestion) {
-        TimestampedValue<Long> newUnixEpochTime = suggestion.getUnixEpochTime();
+    private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) {
+        UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
 
         int slotIndex = suggestion.getSlotIndex();
         TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
         if (previousSuggestion != null) {
-            // We can log / discard suggestions with obvious issues with the reference time clock.
-            if (previousSuggestion.getUnixEpochTime() == null
-                    || previousSuggestion.getUnixEpochTime().getValue() == null) {
+            // We can log / discard suggestions with obvious issues with the elapsed realtime clock.
+            if (previousSuggestion.getUnixEpochTime() == null) {
                 // This should be impossible given we only store validated suggestions.
                 Slog.w(LOG_TAG, "Previous suggestion is null or has a null time."
                         + " previousSuggestion=" + previousSuggestion
@@ -403,10 +481,10 @@
                 return false;
             }
 
-            long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
+            long referenceTimeDifference = UnixEpochTime.elapsedRealtimeDifference(
                     newUnixEpochTime, previousSuggestion.getUnixEpochTime());
             if (referenceTimeDifference < 0) {
-                // The reference time is before the previously received suggestion. Ignore it.
+                // The elapsed realtime is before the previously received suggestion. Ignore it.
                 Slog.w(LOG_TAG, "Out of order telephony suggestion received."
                         + " referenceTimeDifference=" + referenceTimeDifference
                         + " previousSuggestion=" + previousSuggestion
@@ -422,23 +500,18 @@
 
     @GuardedBy("this")
     private boolean validateSuggestionCommon(
-            @NonNull TimestampedValue<Long> newUnixEpochTime, @NonNull Object suggestion) {
-        if (newUnixEpochTime.getValue() == null) {
-            Slog.w(LOG_TAG, "Suggested time value is null. suggestion=" + suggestion);
-            return false;
-        }
-
-        // We can validate the suggestion against the reference time clock.
+            @NonNull UnixEpochTime newUnixEpochTime, @NonNull Object suggestion) {
+        // We can validate the suggestion against the elapsed realtime clock.
         long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
-        if (elapsedRealtimeMillis < newUnixEpochTime.getReferenceTimeMillis()) {
+        if (elapsedRealtimeMillis < newUnixEpochTime.getElapsedRealtimeMillis()) {
             // elapsedRealtime clock went backwards?
-            Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
+            Slog.w(LOG_TAG, "New elapsed realtime is in the future? Ignoring."
                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                     + ", suggestion=" + suggestion);
             return false;
         }
 
-        if (newUnixEpochTime.getValue()
+        if (newUnixEpochTime.getUnixEpochTimeMillis()
                 > mCurrentConfigurationInternal.getSuggestionUpperBound().toEpochMilli()) {
             // This check won't prevent a device's system clock exceeding Integer.MAX_VALUE Unix
             // seconds through the normal passage of time, but it will stop it jumping above 2038
@@ -452,11 +525,11 @@
 
     /**
      * Returns {@code true} if an automatic time suggestion time is valid.
-     * See also {@link #validateManualSuggestionTime(TimestampedValue, Object)}.
+     * See also {@link #validateManualSuggestionTime(UnixEpochTime, Object)}.
      */
     @GuardedBy("this")
     private boolean validateAutoSuggestionTime(
-            @NonNull TimestampedValue<Long> newUnixEpochTime, @NonNull Object suggestion)  {
+            @NonNull UnixEpochTime newUnixEpochTime, @NonNull Object suggestion)  {
         Instant lowerBound = mCurrentConfigurationInternal.getAutoSuggestionLowerBound();
         return validateSuggestionCommon(newUnixEpochTime, suggestion)
                 && validateSuggestionAgainstLowerBound(newUnixEpochTime, suggestion,
@@ -465,11 +538,11 @@
 
     /**
      * Returns {@code true} if a manual time suggestion time is valid.
-     * See also {@link #validateAutoSuggestionTime(TimestampedValue, Object)}.
+     * See also {@link #validateAutoSuggestionTime(UnixEpochTime, Object)}.
      */
     @GuardedBy("this")
     private boolean validateManualSuggestionTime(
-            @NonNull TimestampedValue<Long> newUnixEpochTime, @NonNull Object suggestion)  {
+            @NonNull UnixEpochTime newUnixEpochTime, @NonNull Object suggestion)  {
         Instant lowerBound = mCurrentConfigurationInternal.getManualSuggestionLowerBound();
 
         // Suggestion is definitely wrong if it comes before lower time bound.
@@ -479,11 +552,11 @@
 
     @GuardedBy("this")
     private boolean validateSuggestionAgainstLowerBound(
-            @NonNull TimestampedValue<Long> newUnixEpochTime, @NonNull Object suggestion,
+            @NonNull UnixEpochTime newUnixEpochTime, @NonNull Object suggestion,
             @NonNull Instant lowerBound) {
 
         // Suggestion is definitely wrong if it comes before lower time bound.
-        if (lowerBound.toEpochMilli() > newUnixEpochTime.getValue()) {
+        if (lowerBound.toEpochMilli() > newUnixEpochTime.getUnixEpochTimeMillis()) {
             Slog.w(LOG_TAG, "Suggestion points to time before lower bound, skipping it. "
                     + "suggestion=" + suggestion + ", lower bound=" + lowerBound);
             return false;
@@ -494,15 +567,10 @@
 
     @GuardedBy("this")
     private void doAutoTimeDetection(@NonNull String detectionReason) {
-        if (!mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
-            return;
-        }
-
         // Try the different origins one at a time.
         int[] originPriorities = mCurrentConfigurationInternal.getAutoOriginPriorities();
         for (int origin : originPriorities) {
-            TimestampedValue<Long> newUnixEpochTime = null;
+            UnixEpochTime newUnixEpochTime = null;
             String cause = null;
             if (origin == ORIGIN_TELEPHONY) {
                 TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion();
@@ -544,7 +612,14 @@
 
             // Update the system clock if a good suggestion has been found.
             if (newUnixEpochTime != null) {
-                setSystemClockIfRequired(origin, newUnixEpochTime, cause);
+                if (mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
+                    setSystemClockAndConfidenceIfRequired(origin, newUnixEpochTime, cause);
+                } else {
+                    // An automatically detected time can be used to raise the confidence in the
+                    // current time even if the device is set to only allow user input for the time
+                    // itself.
+                    upgradeSystemClockConfidenceIfRequired(newUnixEpochTime, cause);
+                }
                 return;
             }
         }
@@ -583,7 +658,7 @@
         // The heuristic works as follows:
         // Recency: The most recent suggestion from each slotIndex is scored. The score is based on
         // a discrete age bucket, i.e. so signals received around the same time will be in the same
-        // bucket, thus applying a loose reference time ordering. The suggestion with the highest
+        // bucket, thus applying a loose elapsed realtime ordering. The suggestion with the highest
         // score is used.
         // Consistency: If there a multiple suggestions with the same score, the suggestion with the
         // lowest slotIndex is always taken.
@@ -632,10 +707,11 @@
     }
 
     private static int scoreTelephonySuggestion(
-            long elapsedRealtimeMillis, @NonNull TelephonyTimeSuggestion timeSuggestion) {
+            @ElapsedRealtimeLong long elapsedRealtimeMillis,
+            @NonNull TelephonyTimeSuggestion timeSuggestion) {
 
         // Validate first.
-        TimestampedValue<Long> unixEpochTime = timeSuggestion.getUnixEpochTime();
+        UnixEpochTime unixEpochTime = timeSuggestion.getUnixEpochTime();
         if (!validateSuggestionUnixEpochTime(elapsedRealtimeMillis, unixEpochTime)) {
             Slog.w(LOG_TAG, "Existing suggestion found to be invalid"
                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
@@ -645,7 +721,7 @@
 
         // The score is based on the age since receipt. Suggestions are bucketed so two
         // suggestions in the same bucket from different slotIndexs are scored the same.
-        long ageMillis = elapsedRealtimeMillis - unixEpochTime.getReferenceTimeMillis();
+        long ageMillis = elapsedRealtimeMillis - unixEpochTime.getElapsedRealtimeMillis();
 
         // Turn the age into a discrete value: 0 <= bucketIndex < TELEPHONY_BUCKET_COUNT.
         int bucketIndex = (int) (ageMillis / TELEPHONY_BUCKET_SIZE_MILLIS);
@@ -667,7 +743,7 @@
             return null;
         }
 
-        TimestampedValue<Long> unixEpochTime = networkSuggestion.getUnixEpochTime();
+        UnixEpochTime unixEpochTime = networkSuggestion.getUnixEpochTime();
         long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUnixEpochTime(elapsedRealTimeMillis, unixEpochTime)) {
             // The latest suggestion is not valid, usually due to its age.
@@ -687,7 +763,7 @@
             return null;
         }
 
-        TimestampedValue<Long> unixEpochTime = gnssTimeSuggestion.getUnixEpochTime();
+        UnixEpochTime unixEpochTime = gnssTimeSuggestion.getUnixEpochTime();
         long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUnixEpochTime(elapsedRealTimeMillis, unixEpochTime)) {
             // The latest suggestion is not valid, usually due to its age.
@@ -707,7 +783,7 @@
             return null;
         }
 
-        TimestampedValue<Long> unixEpochTime = externalTimeSuggestion.getUnixEpochTime();
+        UnixEpochTime unixEpochTime = externalTimeSuggestion.getUnixEpochTime();
         long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUnixEpochTime(elapsedRealTimeMillis, unixEpochTime)) {
             // The latest suggestion is not valid, usually due to its age.
@@ -718,14 +794,18 @@
     }
 
     @GuardedBy("this")
-    private boolean setSystemClockIfRequired(
-            @Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
+    private boolean setSystemClockAndConfidenceIfRequired(
+            @Origin int origin, @NonNull UnixEpochTime time, @NonNull String cause) {
 
+        // Any time set through this class is inherently high confidence. Either it came directly
+        // from a user, or it was detected automatically.
+        @TimeConfidence final int newTimeConfidence = TIME_CONFIDENCE_HIGH;
         boolean isOriginAutomatic = isOriginAutomatic(origin);
         if (isOriginAutomatic) {
             if (!mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
                 if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time detection is not enabled."
+                    Slog.d(LOG_TAG,
+                            "Auto time detection is not enabled / no confidence update is needed."
                             + " origin=" + originToString(origin)
                             + ", time=" + time
                             + ", cause=" + cause);
@@ -746,7 +826,54 @@
 
         mEnvironment.acquireWakeLock();
         try {
-            return setSystemClockUnderWakeLock(origin, time, cause);
+            return setSystemClockAndConfidenceUnderWakeLock(origin, time, newTimeConfidence, cause);
+        } finally {
+            mEnvironment.releaseWakeLock();
+        }
+    }
+
+    /**
+     * Upgrades the system clock confidence if the current time matches the supplied auto-detected
+     * time. The method never changes the system clock and it never lowers the confidence. It only
+     * raises the confidence if the supplied time is within the configured threshold of the current
+     * system clock time.
+     */
+    @GuardedBy("this")
+    private void upgradeSystemClockConfidenceIfRequired(
+            @NonNull UnixEpochTime autoDetectedUnixEpochTime, @NonNull String cause) {
+
+        // Fast path: No need to upgrade confidence if confidence is already high.
+        @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
+        @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
+        boolean confidenceUpgradeRequired = currentTimeConfidence < newTimeConfidence;
+        if (!confidenceUpgradeRequired) {
+            return;
+        }
+
+        // All system clock calculation take place under a wake lock.
+        mEnvironment.acquireWakeLock();
+        try {
+            // Check if the specified time matches the current system clock time (closely
+            // enough) to raise the confidence.
+            long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+            long currentSystemClockMillis = mEnvironment.systemClockMillis();
+            boolean updateConfidenceRequired = isTimeWithinConfidenceThreshold(
+                    autoDetectedUnixEpochTime, currentElapsedRealtimeMillis,
+                    currentSystemClockMillis);
+            if (updateConfidenceRequired) {
+                String logMsg = "Upgrade system clock confidence."
+                        + " autoDetectedUnixEpochTime=" + autoDetectedUnixEpochTime
+                        + " newTimeConfidence=" + newTimeConfidence
+                        + " cause=" + cause
+                        + " currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis
+                        + " currentSystemClockMillis=" + currentSystemClockMillis
+                        + " currentTimeConfidence=" + currentTimeConfidence;
+                if (DBG) {
+                    Slog.d(LOG_TAG, logMsg);
+                }
+
+                mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
+            }
         } finally {
             mEnvironment.releaseWakeLock();
         }
@@ -757,8 +884,22 @@
     }
 
     @GuardedBy("this")
-    private boolean setSystemClockUnderWakeLock(
-            @Origin int origin, @NonNull TimestampedValue<Long> newTime, @NonNull String cause) {
+    private boolean isTimeWithinConfidenceThreshold(@NonNull UnixEpochTime timeToCheck,
+            @ElapsedRealtimeLong long currentElapsedRealtimeMillis,
+            @CurrentTimeMillisLong long currentSystemClockMillis) {
+        long adjustedAutoDetectedUnixEpochMillis =
+                timeToCheck.at(currentElapsedRealtimeMillis).getUnixEpochTimeMillis();
+        long absTimeDifferenceMillis =
+                Math.abs(adjustedAutoDetectedUnixEpochMillis - currentSystemClockMillis);
+        int confidenceUpgradeThresholdMillis =
+                mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
+        return absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
+    }
+
+    @GuardedBy("this")
+    private boolean setSystemClockAndConfidenceUnderWakeLock(
+            @Origin int origin, @NonNull UnixEpochTime newTime,
+            @TimeConfidence int newTimeConfidence, @NonNull String cause) {
 
         long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
         boolean isOriginAutomatic = isOriginAutomatic(origin);
@@ -767,8 +908,8 @@
             // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
             // may be setting the clock.
             if (mLastAutoSystemClockTimeSet != null) {
-                long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
-                        mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
+                long expectedTimeMillis = mLastAutoSystemClockTimeSet.at(elapsedRealtimeMillis)
+                        .getUnixEpochTimeMillis();
                 long absSystemClockDifference =
                         Math.abs(expectedTimeMillis - actualSystemClockMillis);
                 if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
@@ -776,6 +917,7 @@
                             "System clock has not tracked elapsed real time clock. A clock may"
                                     + " be inaccurate or something unexpectedly set the system"
                                     + " clock."
+                                    + " origin=" + originToString(origin)
                                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                                     + " expectedTimeMillis=" + expectedTimeMillis
                                     + " actualTimeMillis=" + actualSystemClockMillis
@@ -784,44 +926,72 @@
             }
         }
 
-        // Adjust for the time that has elapsed since the signal was received.
-        long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
+        // If the new signal would make sufficient difference to the system clock or mean a change
+        // in confidence then system state must be updated.
 
-        // Check if the new signal would make sufficient difference to the system clock. If it's
-        // below the threshold then ignore it.
+        // Adjust for the time that has elapsed since the signal was received.
+        long newSystemClockMillis = newTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis();
         long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
         long systemClockUpdateThreshold =
                 mCurrentConfigurationInternal.getSystemClockUpdateThresholdMillis();
-        if (absTimeDifference < systemClockUpdateThreshold) {
+        boolean updateSystemClockRequired = absTimeDifference >= systemClockUpdateThreshold;
+
+        @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
+        boolean updateConfidenceRequired = newTimeConfidence != currentTimeConfidence;
+
+        if (updateSystemClockRequired) {
+            String logMsg = "Set system clock & confidence."
+                    + " origin=" + originToString(origin)
+                    + " newTime=" + newTime
+                    + " newTimeConfidence=" + newTimeConfidence
+                    + " cause=" + cause
+                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                    + " (old) actualSystemClockMillis=" + actualSystemClockMillis
+                    + " newSystemClockMillis=" + newSystemClockMillis
+                    + " currentTimeConfidence=" + currentTimeConfidence;
+            mEnvironment.setSystemClock(newSystemClockMillis, newTimeConfidence, logMsg);
             if (DBG) {
-                Slog.d(LOG_TAG, "Not setting system clock. New time and"
-                        + " system clock are close enough."
-                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                        + " newTime=" + newTime
-                        + " cause=" + cause
-                        + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
-                        + " absTimeDifference=" + absTimeDifference);
+                Slog.d(LOG_TAG, logMsg);
             }
-            return true;
-        }
 
-        mEnvironment.setSystemClock(newSystemClockMillis);
-        String logMsg = "Set system clock using time=" + newTime
-                + " cause=" + cause
-                + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                + " (old) actualSystemClockMillis=" + actualSystemClockMillis
-                + " newSystemClockMillis=" + newSystemClockMillis;
-        if (DBG) {
-            Slog.d(LOG_TAG, logMsg);
-        }
-        mTimeChangesLog.log(logMsg);
-
-        // CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time
-        // signal, or clear the record it is being done manually.
-        if (isOriginAutomatic(origin)) {
-            mLastAutoSystemClockTimeSet = newTime;
+            // CLOCK_PARANOIA : Record the last time this class set the system clock due to an
+            // auto-time signal, or clear the record it is being done manually.
+            if (isOriginAutomatic(origin)) {
+                mLastAutoSystemClockTimeSet = newTime;
+            } else {
+                mLastAutoSystemClockTimeSet = null;
+            }
+        } else if (updateConfidenceRequired) {
+            // Only the confidence needs updating. This path is separate from a system clock update
+            // to deliberately avoid touching the system clock's value when it's not needed. Doing
+            // so could introduce inaccuracies or cause unnecessary wear in RTC hardware or
+            // associated storage.
+            String logMsg = "Set system clock confidence."
+                    + " origin=" + originToString(origin)
+                    + " newTime=" + newTime
+                    + " newTimeConfidence=" + newTimeConfidence
+                    + " cause=" + cause
+                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                    + " (old) actualSystemClockMillis=" + actualSystemClockMillis
+                    + " newSystemClockMillis=" + newSystemClockMillis
+                    + " currentTimeConfidence=" + currentTimeConfidence;
+            if (DBG) {
+                Slog.d(LOG_TAG, logMsg);
+            }
+            mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
         } else {
-            mLastAutoSystemClockTimeSet = null;
+            // Neither clock nor confidence need updating.
+            if (DBG) {
+                Slog.d(LOG_TAG, "Not setting system clock or confidence."
+                        + " origin=" + originToString(origin)
+                        + " newTime=" + newTime
+                        + " newTimeConfidence=" + newTimeConfidence
+                        + " cause=" + cause
+                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                        + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
+                        + " absTimeDifference=" + absTimeDifference
+                        + " currentTimeConfidence=" + currentTimeConfidence);
+            }
         }
         return true;
     }
@@ -902,21 +1072,22 @@
     }
 
     private static boolean validateSuggestionUnixEpochTime(
-            long elapsedRealtimeMillis, TimestampedValue<Long> unixEpochTime) {
-        long referenceTimeMillis = unixEpochTime.getReferenceTimeMillis();
-        if (referenceTimeMillis > elapsedRealtimeMillis) {
-            // Future reference times are ignored. They imply the reference time was wrong, or the
-            // elapsed realtime clock used to derive it has gone backwards, neither of which are
+            @ElapsedRealtimeLong long currentElapsedRealtimeMillis,
+            @NonNull UnixEpochTime unixEpochTime) {
+        long suggestionElapsedRealtimeMillis = unixEpochTime.getElapsedRealtimeMillis();
+        if (suggestionElapsedRealtimeMillis > currentElapsedRealtimeMillis) {
+            // Future elapsed realtimes are ignored. They imply the elapsed realtime was wrong, or
+            // the elapsed realtime clock used to derive it has gone backwards, neither of which are
             // supportable situations.
             return false;
         }
 
         // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and
-        // predictable, the accuracy of the reference time clock may be poor over long periods which
-        // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been
-        // made and never replaced, it could also mean that the time detection code remains
+        // predictable, the accuracy of the elapsed realtime clock may be poor over long periods
+        // which would lead to errors creeping in. Also, in edge cases where a bad suggestion has
+        // been made and never replaced, it could also mean that the time detection code remains
         // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS.
-        long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
+        long ageMillis = currentElapsedRealtimeMillis - suggestionElapsedRealtimeMillis;
         return ageMillis <= MAX_SUGGESTION_TIME_AGE_MILLIS;
     }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index ef99d61..d413feb 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -243,7 +243,7 @@
         } else {
             suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
         }
-        builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
+        builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
 
         return builder.build();
     }
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 0ec8826c..4749f73 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,13 +18,17 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 
+import com.android.server.AlarmManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemTimeZone;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
+
+import java.io.PrintWriter;
 import java.util.Objects;
 
 /**
@@ -54,36 +58,43 @@
     }
 
     @Override
+    @NonNull
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
         return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
     }
 
     @Override
-    public boolean isDeviceTimeZoneInitialized() {
-        // timezone.equals("GMT") will be true and only true if the time zone was
-        // set to a default value by the system server (when starting, system server
-        // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
-        // any code that sets it explicitly (in case where something sets GMT explicitly,
-        // "Etc/GMT" Olson ID would be used).
-
-        String timeZoneId = getDeviceTimeZone();
-        return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
-    }
-
-    @Override
-    @Nullable
+    @NonNull
     public String getDeviceTimeZone() {
         return SystemProperties.get(TIMEZONE_PROPERTY);
     }
 
     @Override
-    public void setDeviceTimeZone(String zoneId) {
-        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
-        alarmManager.setTimeZone(zoneId);
+    public @TimeZoneConfidence int getDeviceTimeZoneConfidence() {
+        return SystemTimeZone.getTimeZoneConfidence();
+    }
+
+    @Override
+    public void setDeviceTimeZoneAndConfidence(
+            @NonNull String zoneId, @TimeZoneConfidence int confidence,
+            @NonNull String logInfo) {
+        AlarmManagerInternal alarmManagerInternal =
+                LocalServices.getService(AlarmManagerInternal.class);
+        alarmManagerInternal.setTimeZone(zoneId, confidence, logInfo);
     }
 
     @Override
     public @ElapsedRealtimeLong long elapsedRealtimeMillis() {
         return SystemClock.elapsedRealtime();
     }
+
+    @Override
+    public void addDebugLogEntry(@NonNull String logMsg) {
+        SystemTimeZone.addDebugLogEntry(logMsg);
+    }
+
+    @Override
+    public void dumpDebugLog(@NonNull PrintWriter printWriter) {
+        SystemTimeZone.dump(printWriter);
+    }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 59db855..822cd41 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -23,6 +23,7 @@
 import android.app.time.ITimeZoneDetectorListener;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -101,8 +102,10 @@
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
-            TimeZoneDetectorService service = TimeZoneDetectorService.create(
-                    context, handler, serviceConfigAccessor, timeZoneDetectorStrategy);
+            CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
+            TimeZoneDetectorService service = new TimeZoneDetectorService(
+                    context, handler, callerIdentityInjector, serviceConfigAccessor,
+                    timeZoneDetectorStrategy);
 
             // Dump the device activity monitor when the service is dumped.
             service.addDumpable(deviceActivityMonitor);
@@ -141,16 +144,6 @@
     @GuardedBy("mDumpables")
     private final List<Dumpable> mDumpables = new ArrayList<>();
 
-    private static TimeZoneDetectorService create(
-            @NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
-            @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
-
-        CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
-        return new TimeZoneDetectorService(context, handler, callerIdentityInjector,
-                serviceConfigAccessor, timeZoneDetectorStrategy);
-    }
-
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
@@ -313,6 +306,57 @@
     }
 
     @Override
+    @NonNull
+    public TimeZoneState getTimeZoneState() {
+        enforceManageTimeZoneDetectorPermission();
+
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
+        try {
+            return mTimeZoneDetectorStrategy.getTimeZoneState();
+        } finally {
+            mCallerIdentityInjector.restoreCallingIdentity(token);
+        }
+    }
+
+    void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
+        enforceManageTimeZoneDetectorPermission();
+
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
+        try {
+            mTimeZoneDetectorStrategy.setTimeZoneState(timeZoneState);
+        } finally {
+            mCallerIdentityInjector.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean confirmTimeZone(@NonNull String timeZoneId) {
+        enforceManageTimeZoneDetectorPermission();
+
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
+        try {
+            return mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId);
+        } finally {
+            mCallerIdentityInjector.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean setManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
+        enforceManageTimeZoneDetectorPermission();
+
+        // This calls suggestManualTimeZone() as the logic is identical, it only differs in the
+        // permission required, which is handled on the line above.
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
+        try {
+            return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
+        } finally {
+            mCallerIdentityInjector.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
     public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
         enforceSuggestManualTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 4d808ff..1b9f8e6 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,8 +15,10 @@
  */
 package com.android.server.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIRM_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -24,6 +26,7 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
@@ -38,6 +41,7 @@
 
 import android.app.time.LocationTimeZoneManager;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.os.ShellCommand;
@@ -83,6 +87,12 @@
                 return runSuggestTelephonyTimeZone();
             case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
                 return runEnableTelephonyFallback();
+            case SHELL_COMMAND_GET_TIME_ZONE_STATE:
+                return runGetTimeZoneState();
+            case SHELL_COMMAND_SET_TIME_ZONE_STATE:
+                return runSetTimeZoneState();
+            case SHELL_COMMAND_CONFIRM_TIME_ZONE:
+                return runConfirmTimeZone();
             case SHELL_COMMAND_DUMP_METRICS:
                 return runDumpMetrics();
             default: {
@@ -183,6 +193,45 @@
         return 0;
     }
 
+    private int runGetTimeZoneState() {
+        TimeZoneState timeZoneState = mInterface.getTimeZoneState();
+        getOutPrintWriter().println(timeZoneState);
+        return 0;
+    }
+
+    private int runSetTimeZoneState() {
+        TimeZoneState timeZoneState = TimeZoneState.parseCommandLineArgs(this);
+        mInterface.setTimeZoneState(timeZoneState);
+        return 0;
+    }
+
+    private int runConfirmTimeZone() {
+        String timeZoneId = parseTimeZoneIdArg(this);
+        getOutPrintWriter().println(mInterface.confirmTimeZone(timeZoneId));
+        return 0;
+    }
+
+    private static String parseTimeZoneIdArg(ShellCommand cmd) {
+        String zoneId = null;
+        String opt;
+        while ((opt = cmd.getNextArg()) != null) {
+            switch (opt) {
+                case "--zone_id": {
+                    zoneId = cmd.getNextArgRequired();
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+
+        if (zoneId == null) {
+            throw new IllegalArgumentException("No zoneId specified.");
+        }
+        return zoneId;
+    }
+
     private int runDumpMetrics() {
         final PrintWriter pw = getOutPrintWriter();
         MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
@@ -226,6 +275,12 @@
         pw.printf("    Suggests a time zone as if via the \"manual\" origin.\n");
         pw.printf("  %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
         pw.printf("    Suggests a time zone as if via the \"telephony\" origin.\n");
+        pw.printf("  %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
+        pw.printf("    Returns the current time zone setting state.\n");
+        pw.printf("  %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
+        pw.printf("    Sets the current time zone state for tests.\n");
+        pw.printf("  %s <--zone_id Olson ID>\n", SHELL_COMMAND_CONFIRM_TIME_ZONE);
+        pw.printf("    Tries to confirms the time zone, raising the confidence.\n");
         pw.printf("  %s\n", SHELL_COMMAND_DUMP_METRICS);
         pw.printf("    Dumps the service metrics to stdout for inspection.\n");
         pw.println();
@@ -235,6 +290,8 @@
         pw.println();
         TelephonyTimeZoneSuggestion.printCommandLineOpts(pw);
         pw.println();
+        TimeZoneState.printCommandLineOpts(pw);
+        pw.println();
         pw.printf("This service is also affected by the following device_config flags in the"
                 + " %s namespace:\n", NAMESPACE_SYSTEM_TIME);
         pw.printf("  %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 95ebd68..e4b2df1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
@@ -93,6 +94,24 @@
  */
 public interface TimeZoneDetectorStrategy extends Dumpable {
 
+    /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
+    @NonNull
+    TimeZoneState getTimeZoneState();
+
+    /**
+     * Sets the system time zone state. See {@link TimeZoneState} for details. Intended for use
+     * during testing to force the device's state, this bypasses the time zone detection logic.
+     */
+    void setTimeZoneState(@NonNull TimeZoneState timeZoneState);
+
+    /**
+     * Signals that a user has confirmed the time zone. If the {@code timeZoneId} is the same as
+     * the current time zone then this can be used to raise the system's confidence in that time
+     * zone. Returns {@code true} if confirmation was successful (i.e. the ID matched),
+     * {@code false} otherwise.
+     */
+    boolean confirmTimeZone(@NonNull String timeZoneId);
+
     /**
      * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
      * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 66c23f5..1e88c47 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -22,24 +22,29 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
+
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.Context;
 import android.os.Handler;
 import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
-import android.util.LocalLog;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
 
+import java.io.PrintWriter;
 import java.time.Duration;
 import java.util.List;
 import java.util.Objects;
@@ -73,19 +78,21 @@
         @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
 
         /**
-         * Returns true if the device has had an explicit time zone set.
+         * Returns the device's currently configured time zone. May return an empty string.
          */
-        boolean isDeviceTimeZoneInitialized();
+        @NonNull String getDeviceTimeZone();
 
         /**
-         * Returns the device's currently configured time zone.
+         * Returns the confidence of the device's current time zone.
          */
-        String getDeviceTimeZone();
+        @TimeZoneConfidence int getDeviceTimeZoneConfidence();
 
         /**
-         * Sets the device's time zone.
+         * Sets the device's time zone, associated confidence, and records a debug log entry.
          */
-        void setDeviceTimeZone(@NonNull String zoneId);
+        void setDeviceTimeZoneAndConfidence(
+                @NonNull String zoneId, @TimeZoneConfidence int confidence,
+                @NonNull String logInfo);
 
         /**
          * Returns the time according to the elapsed realtime clock, the same as {@link
@@ -93,6 +100,16 @@
          */
         @ElapsedRealtimeLong
         long elapsedRealtimeMillis();
+
+        /**
+         * Adds a standalone entry to the time zone debug log.
+         */
+        void addDebugLogEntry(@NonNull String logMsg);
+
+        /**
+         * Dumps the time zone debug log to the supplied {@link PrintWriter}.
+         */
+        void dumpDebugLog(PrintWriter printWriter);
     }
 
     private static final String LOG_TAG = TimeZoneDetectorService.TAG;
@@ -165,13 +182,6 @@
     private final Environment mEnvironment;
 
     /**
-     * A log that records the decisions / decision metadata that affected the device's time zone.
-     * This is logged in bug reports to assist with debugging issues with detection.
-     */
-    @NonNull
-    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
-    /**
      * A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two
      * mappings: devices will have a small number of telephony devices and slotIndexes are assumed
      * to be stable.
@@ -206,8 +216,8 @@
      *
      * <p>This field is only actually used when telephony time zone fallback is supported, but the
      * value is maintained even when it isn't supported as it can be turned on at any time via
-     * server flags. The reference time is the elapsed realtime when the mode last changed to help
-     * ordering between fallback mode switches and suggestions.
+     * server flags. The elapsed realtime when the mode last changed is used to help ordering
+     * between fallback mode switches and suggestions.
      *
      * <p>See {@link TimeZoneDetectorStrategy} for more information.
      */
@@ -242,6 +252,39 @@
     }
 
     @Override
+    public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
+        Objects.requireNonNull(timeZoneId);
+
+        String currentTimeZoneId = mEnvironment.getDeviceTimeZone();
+        if (!currentTimeZoneId.equals(timeZoneId)) {
+            return false;
+        }
+
+        if (mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH) {
+            mEnvironment.setDeviceTimeZoneAndConfidence(currentTimeZoneId,
+                    TIME_ZONE_CONFIDENCE_HIGH, "confirmTimeZone: timeZoneId=" + timeZoneId);
+        }
+        return true;
+    }
+
+    @Override
+    public synchronized TimeZoneState getTimeZoneState() {
+        boolean userShouldConfirmId =
+                mEnvironment.getDeviceTimeZoneConfidence() < TIME_ZONE_CONFIDENCE_HIGH;
+        return new TimeZoneState(mEnvironment.getDeviceTimeZone(), userShouldConfirmId);
+    }
+
+    @Override
+    public void setTimeZoneState(@NonNull TimeZoneState timeZoneState) {
+        Objects.requireNonNull(timeZoneState);
+
+        @TimeZoneConfidence int confidence = timeZoneState.getUserShouldConfirmId()
+                ? TIME_ZONE_CONFIDENCE_LOW : TIME_ZONE_CONFIDENCE_HIGH;
+        mEnvironment.setDeviceTimeZoneAndConfidence(
+                timeZoneState.getId(), confidence, "setTimeZoneState()");
+    }
+
+    @Override
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
 
@@ -293,9 +336,9 @@
         TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                 currentUserConfig.createCapabilitiesAndConfig();
         TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
+        if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
-                    + ", capabilities=" + capabilities
+                    + ": capabilities=" + capabilities
                     + ", timeZoneId=" + timeZoneId
                     + ", cause=" + cause);
             return false;
@@ -345,11 +388,11 @@
             mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
                     mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
 
-            String logMsg = "enableTelephonyTimeZoneFallbackMode"
-                    + ": currentUserConfig=" + currentUserConfig
+            String logMsg = "enableTelephonyTimeZoneFallbackMode: "
+                    + " currentUserConfig=" + currentUserConfig
                     + ", mTelephonyTimeZoneFallbackEnabled="
                     + mTelephonyTimeZoneFallbackEnabled;
-            logTimeZoneDetectorChange(logMsg);
+            logTimeZoneDebugInfo(logMsg);
 
             // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
             // If there is currently a certain geolocation suggestion, then the telephony fallback
@@ -554,19 +597,18 @@
                 mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
                         mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
 
-                String logMsg = "disableTelephonyFallbackIfNeeded"
-                        + ": mTelephonyTimeZoneFallbackEnabled="
-                        + mTelephonyTimeZoneFallbackEnabled;
-                logTimeZoneDetectorChange(logMsg);
+                String logMsg = "disableTelephonyFallbackIfNeeded:"
+                        + " mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled;
+                logTimeZoneDebugInfo(logMsg);
             }
         }
     }
 
-    private void logTimeZoneDetectorChange(@NonNull String logMsg) {
+    private void logTimeZoneDebugInfo(@NonNull String logMsg) {
         if (DBG) {
             Slog.d(LOG_TAG, logMsg);
         }
-        mTimeZoneChangesLog.log(logMsg);
+        mEnvironment.addDebugLogEntry(logMsg);
     }
 
     /**
@@ -594,7 +636,7 @@
                 bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD;
         if (!suggestionGoodEnough) {
             if (DBG) {
-                Slog.d(LOG_TAG, "Best suggestion not good enough."
+                Slog.d(LOG_TAG, "Best suggestion not good enough:"
                         + " bestTelephonySuggestion=" + bestTelephonySuggestion
                         + ", detectionReason=" + detectionReason);
             }
@@ -607,12 +649,12 @@
         if (zoneId == null) {
             Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
                     + " bestTelephonySuggestion=" + bestTelephonySuggestion
-                    + " detectionReason=" + detectionReason);
+                    + ", detectionReason=" + detectionReason);
             return;
         }
 
-        String cause = "Found good suggestion."
-                + ", bestTelephonySuggestion=" + bestTelephonySuggestion
+        String cause = "Found good suggestion:"
+                + " bestTelephonySuggestion=" + bestTelephonySuggestion
                 + ", detectionReason=" + detectionReason;
         setDeviceTimeZoneIfRequired(zoneId, cause);
     }
@@ -620,26 +662,33 @@
     @GuardedBy("this")
     private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
         String currentZoneId = mEnvironment.getDeviceTimeZone();
+        // All manual and automatic suggestions are considered high confidence as low-quality
+        // suggestions are not currently passed on.
+        int newConfidence = TIME_ZONE_CONFIDENCE_HIGH;
+        int currentConfidence = mEnvironment.getDeviceTimeZoneConfidence();
 
-        // Avoid unnecessary changes / intents.
-        if (newZoneId.equals(currentZoneId)) {
-            // No need to set the device time zone - the setting is already what we would be
-            // suggesting.
+        // Avoid unnecessary changes / intents. If the newConfidence is higher than the stored value
+        // then we want to upgrade it.
+        if (newZoneId.equals(currentZoneId) && newConfidence <= currentConfidence) {
+            // No need to modify the device time zone settings.
             if (DBG) {
-                Slog.d(LOG_TAG, "No need to change the time zone;"
-                        + " device is already set to newZoneId."
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
+                Slog.d(LOG_TAG, "No need to change the time zone device is already set to newZoneId"
+                        + ": newZoneId=" + newZoneId
+                        + ", cause=" + cause
+                        + ", currentScore=" + currentConfidence
+                        + ", newConfidence=" + newConfidence);
             }
             return;
         }
 
-        mEnvironment.setDeviceTimeZone(newZoneId);
-        String logMsg = "Set device time zone."
-                + ", currentZoneId=" + currentZoneId
-                + ", newZoneId=" + newZoneId
-                + ", cause=" + cause;
-        logTimeZoneDetectorChange(logMsg);
+        String logInfo = "Set device time zone or higher confidence:"
+                + " newZoneId=" + newZoneId
+                + ", cause=" + cause
+                + ", newConfidence=" + newConfidence;
+        if (DBG) {
+            Slog.d(LOG_TAG, logInfo);
+        }
+        mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence, logInfo);
     }
 
     @GuardedBy("this")
@@ -691,7 +740,7 @@
         String logMsg = "handleConfigurationInternalChanged:"
                 + " oldConfiguration=" + mCurrentConfigurationInternal
                 + ", newConfiguration=" + currentUserConfig;
-        logTimeZoneDetectorChange(logMsg);
+        logTimeZoneDebugInfo(logMsg);
         mCurrentConfigurationInternal = currentUserConfig;
 
         // The configuration change may have changed available suggestions or the way suggestions
@@ -710,9 +759,9 @@
         ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
         ipw.println("[Capabilities=" + mCurrentConfigurationInternal.createCapabilitiesAndConfig()
                 + "]");
-        ipw.println("mEnvironment.isDeviceTimeZoneInitialized()="
-                + mEnvironment.isDeviceTimeZoneInitialized());
         ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
+        ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
+                + mEnvironment.getDeviceTimeZoneConfidence());
 
         ipw.println("Misc state:");
         ipw.increaseIndent(); // level 2
@@ -720,9 +769,9 @@
                 + formatDebugString(mTelephonyTimeZoneFallbackEnabled));
         ipw.decreaseIndent(); // level 2
 
-        ipw.println("Time zone change log:");
+        ipw.println("Time zone debug log:");
         ipw.increaseIndent(); // level 2
-        mTimeZoneChangesLog.dump(ipw);
+        mEnvironment.dumpDebugLog(ipw);
         ipw.decreaseIndent(); // level 2
 
         ipw.println("Manual suggestion history:");
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 948439d..2d022ae 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -189,6 +189,13 @@
             if (!parent.exists()) {
                 throw new IOException("Failed to create directory " + parent.getCanonicalPath());
             }
+
+            // Give executable permissions to parent folders.
+            while (!(parent.equals(updateDir))) {
+                parent.setExecutable(true, false);
+                parent = parent.getParentFile();
+            }
+
             // create the temporary file
             tmp = File.createTempFile("journal", "", dir);
             // mark tmp -rw-r--r--
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d0b058b..e197319 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1337,6 +1337,7 @@
                     }
                     FgThread.getHandler().removeCallbacks(mResetRunnable);
                     mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
+                    mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 804689a..44b83096 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -58,6 +58,7 @@
 import android.graphics.BLASTBufferQueue;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -1021,6 +1022,27 @@
                 }
             }
 
+            private Region getLetterboxBounds(WindowState windowState) {
+                final ActivityRecord appToken = windowState.mActivityRecord;
+                if (appToken == null) {
+                    return new Region();
+                }
+
+                final Rect boundsWithoutLetterbox = windowState.getBounds();
+                final Rect letterboxInsets = appToken.getLetterboxInsets();
+
+                final Rect boundsIncludingLetterbox = Rect.copyOrNull(boundsWithoutLetterbox);
+                // Letterbox insets from mActivityRecord are positive, so we negate them to grow the
+                // bounds to include the letterbox.
+                boundsIncludingLetterbox.inset(
+                        Insets.subtract(Insets.NONE, Insets.of(letterboxInsets)));
+
+                final Region letterboxBounds = new Region();
+                letterboxBounds.set(boundsIncludingLetterbox);
+                letterboxBounds.op(boundsWithoutLetterbox, Region.Op.DIFFERENCE);
+                return letterboxBounds;
+            }
+
             private boolean isExcludedWindowType(int windowType) {
                 return windowType == TYPE_MAGNIFICATION_OVERLAY
                         // Omit the touch region of window magnification to avoid the cut out of the
@@ -1411,20 +1433,6 @@
         return source != null ? source.getFrame() : EMPTY_RECT;
     }
 
-    static Region getLetterboxBounds(WindowState windowState) {
-        final ActivityRecord appToken = windowState.mActivityRecord;
-        if (appToken == null) {
-            return new Region();
-        }
-        final Rect letterboxInsets = appToken.getLetterboxInsets();
-        final Rect nonLetterboxRect = windowState.getBounds();
-        nonLetterboxRect.inset(letterboxInsets);
-        final Region letterboxBounds = new Region();
-        letterboxBounds.set(windowState.getBounds());
-        letterboxBounds.op(nonLetterboxRect, Region.Op.DIFFERENCE);
-        return letterboxBounds;
-    }
-
     /**
      * This class encapsulates the functionality related to computing the windows
      * reported for accessibility purposes. These windows are all windows a sighted
@@ -1678,18 +1686,17 @@
                 a11yWindow.getTouchableRegionInScreen(touchableRegion);
                 unaccountedSpace.op(touchableRegion, unaccountedSpace,
                         Region.Op.REVERSE_DIFFERENCE);
-                // Account for the space of letterbox.
-                final Region letterboxBounds = mTempRegion1;
-                if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) {
-                    unaccountedSpace.op(letterboxBounds,
-                            unaccountedSpace, Region.Op.REVERSE_DIFFERENCE);
-                }
             }
         }
 
         private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
                 Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
             final WindowInfo window = a11yWindow.getWindowInfo();
+            if (window.token == null) {
+                // The window was used in calculating visible windows but does not have an
+                // associated IWindow token, so exclude it from the list returned to accessibility.
+                return;
+            }
             window.regionInScreen.set(regionInScreen);
             window.layer = tokenOut.size();
             out.add(window);
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 79ae662..d0c381e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -153,7 +153,11 @@
         for (InputWindowHandle window : windowHandles) {
             final boolean visible = (window.inputConfig & InputConfig.NOT_VISIBLE) == 0;
             final boolean isNotClone = (window.inputConfig & InputConfig.CLONE) == 0;
-            if (visible && window.getWindow() != null && isNotClone) {
+            final boolean hasTouchableRegion = !window.touchableRegion.isEmpty();
+            final boolean hasNonEmptyFrame =
+                    (window.frameBottom != window.frameTop) && (window.frameLeft
+                            != window.frameRight);
+            if (visible && isNotClone && hasTouchableRegion && hasNonEmptyFrame) {
                 tempVisibleWindows.add(window);
             }
         }
@@ -321,10 +325,20 @@
         // the old and new windows at the same index should be the
         // same, otherwise something changed.
         for (int i = 0; i < windowsCount; i++) {
-            final InputWindowHandle newWindow = newWindows.get(i);
-            final InputWindowHandle oldWindow = oldWindows.get(i);
+            final IWindow newWindowToken = newWindows.get(i).getWindow();
+            final IWindow oldWindowToken = oldWindows.get(i).getWindow();
+            final boolean hasNewWindowToken = newWindowToken != null;
+            final boolean hasOldWindowToken = oldWindowToken != null;
 
-            if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) {
+            // If window token presence has changed then the windows have changed.
+            if (hasNewWindowToken != hasOldWindowToken) {
+                return true;
+            }
+
+            // If both old and new windows had window tokens, but those tokens differ,
+            // then the windows have changed.
+            if (hasNewWindowToken && hasOldWindowToken
+                    && !newWindowToken.asBinder().equals(oldWindowToken.asBinder())) {
                 return true;
             }
         }
@@ -374,7 +388,8 @@
         for (int index = inputWindowHandles.size() - 1; index >= 0; index--) {
             final Matrix windowTransformMatrix = mTempMatrix2;
             final InputWindowHandle windowHandle = inputWindowHandles.get(index);
-            final IBinder iBinder = windowHandle.getWindow().asBinder();
+            final IBinder iBinder =
+                    windowHandle.getWindow() != null ? windowHandle.getWindow().asBinder() : null;
 
             if (getWindowTransformMatrix(iBinder, windowTransformMatrix)) {
                 generateMagnificationSpecInverseMatrix(windowHandle, currentMagnificationSpec,
@@ -609,7 +624,6 @@
         private boolean mIgnoreDuetoRecentsAnimation;
         private final Region mTouchableRegionInScreen = new Region();
         private final Region mTouchableRegionInWindow = new Region();
-        private final Region mLetterBoxBounds = new Region();
         private WindowInfo mWindowInfo;
 
 
@@ -628,11 +642,11 @@
 
             final AccessibilityWindow instance = new AccessibilityWindow();
 
-            instance.mWindow = inputWindowHandle.getWindow();
+            instance.mWindow = window;
             instance.mDisplayId = inputWindowHandle.displayId;
             instance.mInputConfig = inputWindowHandle.inputConfig;
             instance.mType = inputWindowHandle.layoutParamsType;
-            instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder);
+            instance.mIsPIPMenu = window != null && window.asBinder().equals(pipIBinder);
 
             // TODO (b/199357848): gets the private flag of the window from other way.
             instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0;
@@ -644,11 +658,6 @@
             instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
                     && controller.shouldIgnoreForAccessibility(windowState);
 
-            // TODO (b/199358388) : gets the letterbox bounds of the window from other way.
-            if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) {
-                getLetterBoxBounds(windowState, instance.mLetterBoxBounds);
-            }
-
             final Rect windowFrame = new Rect(inputWindowHandle.frameLeft,
                     inputWindowHandle.frameTop, inputWindowHandle.frameRight,
                     inputWindowHandle.frameBottom);
@@ -724,21 +733,6 @@
         }
 
         /**
-         * Gets the letter box bounds if activity bounds are letterboxed
-         * or letterboxed for display cutout.
-         *
-         * @return {@code true} there's a letter box bounds.
-         */
-        public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) {
-            if (mLetterBoxBounds.isEmpty()) {
-                return false;
-            }
-
-            outBounds.set(mLetterBoxBounds);
-            return true;
-        }
-
-        /**
          * @return true if this window should be magnified.
          */
         public boolean shouldMagnify() {
@@ -841,7 +835,7 @@
             WindowInfo windowInfo = WindowInfo.obtain();
             windowInfo.displayId = window.mDisplayId;
             windowInfo.type = window.mType;
-            windowInfo.token = window.mWindow.asBinder();
+            windowInfo.token = window.mWindow != null ? window.mWindow.asBinder() : null;
             windowInfo.hasFlagWatchOutsideTouch = (window.mInputConfig
                     & InputConfig.WATCH_OUTSIDE_TOUCH) != 0;
             // Set it to true to be consistent with the legacy implementation.
@@ -849,18 +843,11 @@
             return windowInfo;
         }
 
-        private static void getLetterBoxBounds(WindowState windowState, Region outRegion) {
-            final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets();
-            final Rect nonLetterboxRect = windowState.getBounds();
-
-            nonLetterboxRect.inset(letterboxInsets);
-            outRegion.set(windowState.getBounds());
-            outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE);
-        }
-
         @Override
         public String toString() {
-            String builder = "A11yWindow=[" + mWindow.asBinder()
+            String windowToken =
+                    mWindow != null ? mWindow.asBinder().toString() : "(no window token)";
+            return "A11yWindow=[" + windowToken
                     + ", displayId=" + mDisplayId
                     + ", inputConfig=0x" + Integer.toHexString(mInputConfig)
                     + ", type=" + mType
@@ -871,12 +858,9 @@
                     + ", isTrustedOverlay=" + isTrustedOverlay()
                     + ", regionInScreen=" + mTouchableRegionInScreen
                     + ", touchableRegion=" + mTouchableRegionInWindow
-                    + ", letterBoxBounds=" + mLetterBoxBounds
                     + ", isPIPMenu=" + mIsPIPMenu
                     + ", windowInfo=" + mWindowInfo
                     + "]";
-
-            return builder;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ffe24c0..59f37c2 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -27,6 +27,8 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
@@ -332,8 +334,8 @@
     }
 
     @Override
-    public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
-            Intent resultData) {
+    public boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType,
+            int resultCode, Intent resultData) {
         final ActivityRecord r;
         synchronized (mGlobalLock) {
             r = ActivityRecord.isInRootTaskLocked(token);
@@ -348,7 +350,7 @@
 
         synchronized (mGlobalLock) {
             return r.getRootTask().navigateUpTo(
-                    r, destIntent, destGrants, resultCode, resultData, resultGrants);
+                    r, destIntent, resolvedType, destGrants, resultCode, resultData, resultGrants);
         }
     }
 
@@ -707,7 +709,26 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                return r != null && r.setOccludesParent(true);
+                // Create a transition if the activity is playing in case the below activity didn't
+                // commit invisible. That's because if any activity below this one has changed its
+                // visibility while playing transition, there won't able to commit visibility until
+                // the running transition finish.
+                final Transition transition = r != null
+                        && r.mTransitionController.inPlayingTransition(r)
+                        ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
+                if (transition != null) {
+                    r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+                final boolean changed = r != null && r.setOccludesParent(true);
+                if (transition != null) {
+                    if (changed) {
+                        r.mTransitionController.setReady(r.getDisplayContent());
+                    } else {
+                        transition.abort();
+                    }
+                }
+                return changed;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -728,7 +749,25 @@
                 if (under != null) {
                     under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
                 }
-                return r.setOccludesParent(false);
+                // Create a transition if the activity is playing in case the current activity
+                // didn't commit invisible. That's because if this activity has changed its
+                // visibility while playing transition, there won't able to commit visibility until
+                // the running transition finish.
+                final Transition transition = r.mTransitionController.inPlayingTransition(r)
+                        ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+                if (transition != null) {
+                    r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+                final boolean changed = r.setOccludesParent(false);
+                if (transition != null) {
+                    if (changed) {
+                        r.mTransitionController.setReady(r.getDisplayContent());
+                    } else {
+                        transition.abort();
+                    }
+                }
+                return changed;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7067ae4..f0de1d3 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -237,9 +237,21 @@
             if (mAssociatedTransitionInfo == null) {
                 launchResult = ":failed";
             } else {
-                launchResult = (abort ? ":canceled:" : mAssociatedTransitionInfo.mProcessSwitch
-                        ? ":completed:" : ":completed-same-process:")
-                        + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
+                final String status;
+                if (abort) {
+                    status = ":canceled:";
+                } else if (!mAssociatedTransitionInfo.mProcessSwitch) {
+                    status = ":completed-same-process:";
+                } else {
+                    if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) {
+                        status = ":completed-hot:";
+                    } else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) {
+                        status = ":completed-warm:";
+                    } else {
+                        status = ":completed-cold:";
+                    }
+                }
+                launchResult = status + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
             }
             // Put a supplement trace as the description of the async trace with the same id.
             Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName + launchResult);
@@ -1241,7 +1253,8 @@
                 info.mSourceEventDelayMs,
                 isIncremental,
                 isLoading,
-                info.mLastLaunchedActivity.info.name.hashCode());
+                info.mLastLaunchedActivity.info.name.hashCode(),
+                TimeUnit.NANOSECONDS.toMillis(info.mLaunchingState.mStartRealtimeNs));
 
         // Ends the trace started at the beginning of this function. This is located here to allow
         // the trace slice to have a noticable duration.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 98d3adc..c317be7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -120,6 +120,8 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -657,7 +659,7 @@
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
-    boolean mUseTransferredAnimation;
+    int mTransitionChangeFlags;
 
     /** Whether we need to setup the animation to animate only within the letterbox. */
     private boolean mNeedsLetterboxedAnimation;
@@ -1354,10 +1356,6 @@
         return true;
     }
 
-    void setAppTimeTracker(AppTimeTracker att) {
-        appTimeTracker = att;
-    }
-
     /** Update the saved state of an activity. */
     void setSavedState(@Nullable Bundle savedState) {
         mIcicle = savedState;
@@ -1586,11 +1584,19 @@
 
         if (oldParent != null) {
             oldParent.cleanUpActivityReferences(this);
+            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+            // client side if needed.
+            oldParent.onActivityVisibleRequestedChanged();
         }
 
-        if (newParent != null && isState(RESUMED)) {
-            newParent.setResumedActivity(this, "onParentChanged");
-            mImeInsetsFrozenUntilStartInput = false;
+        if (newParent != null) {
+            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+            // client side if needed.
+            newParent.onActivityVisibleRequestedChanged();
+            if (isState(RESUMED)) {
+                newParent.setResumedActivity(this, "onParentChanged");
+                mImeInsetsFrozenUntilStartInput = false;
+            }
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -4380,10 +4386,10 @@
                     // When transferring an animation, we no longer need to apply an animation to
                     // the token we transfer the animation over. Thus, set this flag to indicate
                     // we've transferred the animation.
-                    mUseTransferredAnimation = true;
+                    mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 } else if (mTransitionController.getTransitionPlayer() != null) {
                     // In the new transit system, just set this every time we transfer the window
-                    mUseTransferredAnimation = true;
+                    mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 }
                 // Post cleanup after the visibility and animation are transferred.
                 fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -5094,6 +5100,10 @@
             return;
         }
         mVisibleRequested = visible;
+        final TaskFragment taskFragment = getTaskFragment();
+        if (taskFragment != null) {
+            taskFragment.onActivityVisibleRequestedChanged();
+        }
         setInsetsFrozen(!visible);
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
@@ -5228,6 +5238,10 @@
 
         // If in a transition, defer commits for activities that are going invisible
         if (!visible && inTransition()) {
+            if (mTransitionController.inPlayingTransition(this)
+                    && mTransitionController.isCollecting(this)) {
+                mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+            }
             return;
         }
         // If we are preparing an app transition, then delay changing
@@ -5278,7 +5292,7 @@
     @Override
     boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
             boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
-        if (mUseTransferredAnimation) {
+        if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
             return false;
         }
         // If it was set to true, reset the last request to force the transition.
@@ -5351,7 +5365,7 @@
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
         displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
-        mUseTransferredAnimation = false;
+        mTransitionChangeFlags = 0;
 
         postApplyAnimation(visible, fromTransition);
     }
@@ -9550,7 +9564,7 @@
 
     @Override
     boolean showToCurrentUser() {
-        return mShowForAllUsers || mWmService.isCurrentProfile(mUserId);
+        return mShowForAllUsers || mWmService.isUserVisible(mUserId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index d131457..8cbd9fc 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@
 import android.util.SparseArray;
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowManager;
+import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -566,14 +567,39 @@
             return false;
         }
         mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
-        final ActivityMetricsLogger.LaunchingState launchingState =
-                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
-        final Task task = r.getTask();
-        mService.deferWindowLayout();
-        try {
+        final RemoteTransition remote = options.getRemoteTransition();
+        if (remote != null && rootTask.mTransitionController.isCollecting()) {
+            final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+                    0 /* flags */, rootTask.mTransitionController,
+                    mService.mWindowManager.mSyncEngine);
+            // Special case: we are entering recents while an existing transition is running. In
+            // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+            // that it can get its own transition and thus update launcher correctly.
+            mService.mWindowManager.mSyncEngine.queueSyncSet(
+                    () -> rootTask.mTransitionController.moveToCollecting(transition),
+                    () -> {
+                        final Task task = r.getTask();
+                        task.mTransitionController.requestStartTransition(transition,
+                                task, remote, null /* displayChange */);
+                        task.mTransitionController.collect(task);
+                        startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+                    });
+        } else {
+            final Task task = r.getTask();
             task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
                     0 /* flags */, task, task /* readyGroupRef */,
                     options.getRemoteTransition(), null /* displayChange */);
+            startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+        }
+        return true;
+    }
+
+    void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+            ActivityRecord r, Task task, Task rootTask) {
+        final ActivityMetricsLogger.LaunchingState launchingState =
+                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+        mService.deferWindowLayout();
+        try {
             r.mTransitionController.setTransientLaunch(r,
                     TaskDisplayArea.getRootTaskAbove(rootTask));
             task.moveToFront("startExistingRecents");
@@ -585,7 +611,6 @@
             task.mInResumeTopActivity = false;
             mService.continueWindowLayout();
         }
-        return true;
     }
 
     void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bc1c6b2..1d70146 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -892,9 +892,10 @@
 
         final int userId = aInfo != null && aInfo.applicationInfo != null
                 ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+        final int launchMode = aInfo != null ? aInfo.launchMode : 0;
         if (err == ActivityManager.START_SUCCESS) {
             Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
-                    + "} from uid " + callingUid);
+                    + "} with " + launchModeToString(launchMode) + " from uid " + callingUid);
         }
 
         ActivityRecord sourceRecord = null;
@@ -1847,7 +1848,7 @@
             ActivityRecord targetTopActivity =
                     targetTask != null ? targetTask.getTopNonFinishingActivity() : null;
             boolean passesAsmChecks = newTask
-                    ? mRootWindowContainer.hasResumedActivity(callerUid)
+                    ? mService.mVisibleActivityProcessTracker.hasResumedActivity(callerUid)
                     : targetTopActivity != null && targetTopActivity.getUid() == callerUid;
 
             if (!passesAsmChecks) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e7b62b0..4d970f0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -313,6 +313,7 @@
     public abstract void onProcessRemoved(String name, int uid);
     public abstract void onCleanUpApplicationRecord(WindowProcessController proc);
     public abstract int getTopProcessState();
+    public abstract boolean useTopSchedGroupForTopProcess();
     public abstract void clearHeavyWeightProcessIfEquals(WindowProcessController proc);
     public abstract void finishHeavyWeightApp();
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e1f40a8..9f27910 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,6 +68,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
@@ -226,6 +227,7 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
+import android.window.BackAnimationAdapter;
 import android.window.BackNavigationInfo;
 import android.window.IWindowOrganizerController;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
@@ -458,7 +460,7 @@
     private final ClientLifecycleManager mLifecycleManager;
 
     @Nullable
-    private final BackNavigationController mBackNavigationController;
+    final BackNavigationController mBackNavigationController;
 
     private TaskChangeNotificationController mTaskChangeNotificationController;
     /** The controller for all operations related to locktask. */
@@ -1445,6 +1447,8 @@
 
     boolean canLaunchDreamActivity(String packageName) {
         if (!mDreaming || packageName == null) {
+            ProtoLog.e(WM_DEBUG_DREAM, "Cannot launch dream activity due to invalid state. "
+                    + "dreaming: %b packageName: %s", mDreaming, packageName);
             return false;
         }
         final DreamManagerInternal dreamManager =
@@ -1460,6 +1464,9 @@
         if (activeDoze != null && packageName.equals(activeDoze.getPackageName())) {
             return true;
         }
+        ProtoLog.e(WM_DEBUG_DREAM,
+                "Dream packageName does not match active dream. Package %s does not match %s or %s",
+                packageName, String.valueOf(activeDream), String.valueOf(activeDoze));
         return false;
     }
 
@@ -1840,14 +1847,15 @@
     }
 
     @Override
-    public BackNavigationInfo startBackNavigation(boolean requestAnimation,
-            IWindowFocusObserver observer) {
+    public BackNavigationInfo startBackNavigation(
+            IWindowFocusObserver observer, BackAnimationAdapter adapter) {
         mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
                 "startBackNavigation()");
         if (mBackNavigationController == null) {
             return null;
         }
-        return mBackNavigationController.startBackNavigation(requestAnimation, observer);
+
+        return mBackNavigationController.startBackNavigation(observer, adapter);
     }
 
     /**
@@ -5751,17 +5759,19 @@
         @HotPath(caller = HotPath.OOM_ADJUSTMENT)
         @Override
         public int getTopProcessState() {
-            final int topState = mTopProcessState;
-            if (mDemoteTopAppReasons != 0 && topState == ActivityManager.PROCESS_STATE_TOP) {
-                // There may be a more important UI/animation than the top app.
-                return ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-            }
             if (mRetainPowerModeAndTopProcessState) {
                 // There is a launching app while device may be sleeping, force the top state so
                 // the launching process can have top-app scheduling group.
                 return ActivityManager.PROCESS_STATE_TOP;
             }
-            return topState;
+            return mTopProcessState;
+        }
+
+        @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+        @Override
+        public boolean useTopSchedGroupForTopProcess() {
+            // If it is non-zero, there may be a more important UI/animation than the top app.
+            return mDemoteTopAppReasons == 0;
         }
 
         @HotPath(caller = HotPath.PROCESS_CHANGE)
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 28cd001..b473700 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -139,7 +139,6 @@
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
@@ -1183,10 +1182,14 @@
         }
 
         if (!displayContent.isPrivate()) {
-            // Anyone can launch on a public display.
-            ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch on public "
-                    + "display");
-            return true;
+            // Checks if the caller can be shown in the given public display.
+            int userId = UserHandle.getUserId(callingUid);
+            int displayId = display.getDisplayId();
+            boolean allowed = mWindowManager.mUmInternal.isUserVisible(userId, displayId);
+            ProtoLog.d(WM_DEBUG_TASKS,
+                    "Launch on display check: %s launch for userId=%d on displayId=%d",
+                    (allowed ? "allow" : "disallow"), userId, displayId);
+            return allowed;
         }
 
         // Check if the caller is the owner of the display.
@@ -1574,17 +1577,12 @@
         if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) {
             removePinnedRootTaskInSurfaceTransaction(rootTask);
         } else {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    ActivityTaskSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
-            rootTask.forAllLeafTasks(c, true /* traverseTopToBottom */);
-            c.recycle();
+            rootTask.forAllLeafTasks(task -> {
+                removeTask(task, true /* killProcess */, REMOVE_FROM_RECENTS, "remove-root-task");
+            }, true /* traverseTopToBottom */);
         }
     }
 
-    private void processRemoveTask(Task task) {
-        removeTask(task, true /* killProcess */, REMOVE_FROM_RECENTS, "remove-root-task");
-    }
-
     /**
      * Removes the root task associated with the given {@param task}. If the {@param task} is the
      * pinned task, then its child tasks are not explicitly removed when the root task is
@@ -2264,23 +2262,17 @@
     }
 
     void scheduleUpdateMultiWindowMode(Task task) {
-        final PooledConsumer c = PooledLambda.obtainConsumer(
-                ActivityTaskSupervisor::addToMultiWindowModeChangedList, this,
-                PooledLambda.__(ActivityRecord.class));
-        task.forAllActivities(c);
-        c.recycle();
+        task.forAllActivities(r -> {
+            if (r.attachedToProcess()) {
+                mMultiWindowModeChangedActivities.add(r);
+            }
+        });
 
         if (!mHandler.hasMessages(REPORT_MULTI_WINDOW_MODE_CHANGED_MSG)) {
             mHandler.sendEmptyMessage(REPORT_MULTI_WINDOW_MODE_CHANGED_MSG);
         }
     }
 
-    private void addToMultiWindowModeChangedList(ActivityRecord r) {
-        if (r.attachedToProcess()) {
-            mMultiWindowModeChangedActivities.add(r);
-        }
-    }
-
     void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Task prevRootTask) {
         final Task rootTask = task.getRootTask();
         if ((prevRootTask == null || (prevRootTask != rootTask
@@ -2292,11 +2284,14 @@
     }
 
     void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) {
-        final PooledConsumer c = PooledLambda.obtainConsumer(
-                ActivityTaskSupervisor::addToPipModeChangedList, this,
-                PooledLambda.__(ActivityRecord.class));
-        task.forAllActivities(c);
-        c.recycle();
+        task.forAllActivities(r -> {
+            if (!r.attachedToProcess()) return;
+            mPipModeChangedActivities.add(r);
+            // If we are scheduling pip change, then remove this activity from multi-window
+            // change list as the processing of pip change will make sure multi-window changed
+            // message is processed in the right order relative to pip changed.
+            mMultiWindowModeChangedActivities.remove(r);
+        });
 
         mPipModeChangedTargetRootTaskBounds = targetRootTaskBounds;
 
@@ -2305,16 +2300,6 @@
         }
     }
 
-    private void addToPipModeChangedList(ActivityRecord r) {
-        if (!r.attachedToProcess()) return;
-
-        mPipModeChangedActivities.add(r);
-        // If we are scheduling pip change, then remove this activity from multi-window
-        // change list as the processing of pip change will make sure multi-window changed
-        // message is processed in the right order relative to pip changed.
-        mMultiWindowModeChangedActivities.remove(r);
-    }
-
     void wakeUp(String reason) {
         mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION,
                 "android.server.am:TURN_ON:" + reason);
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index df72260..d42a74f 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -70,6 +70,9 @@
             preDumpIfLockTooSlow();
             final ActivityRecord activity;
             timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
+            boolean blamePendingFocusRequest = false;
+            IBinder focusToken = null;
+            WindowState targetWindowState = null;
             synchronized (mService.mGlobalLock) {
                 timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
                 activity = ActivityRecord.forTokenLocked(applicationHandle.token);
@@ -82,35 +85,36 @@
                 // App is unresponsive, but we are actively trying to give focus to a window.
                 // Blame the window if possible since the window may not belong to the app.
                 DisplayContent display = mService.mRoot.getDisplayContent(activity.getDisplayId());
-                IBinder focusToken =
-                        display == null ? null : display.getInputMonitor().mInputFocus;
+                if (display != null) {
+                    focusToken = display.getInputMonitor().mInputFocus;
+                }
                 InputTarget focusTarget = mService.getInputTargetFromToken(focusToken);
 
                 if (focusTarget != null) {
                     // Check if we have a recent focus request, newer than the dispatch timeout,
                     // then ignore the focus request.
-                    WindowState targetWindowState = focusTarget.getWindowState();
-                    boolean requestIsValid = SystemClock.uptimeMillis()
+                    targetWindowState = focusTarget.getWindowState();
+                    blamePendingFocusRequest = SystemClock.uptimeMillis()
                             - display.getInputMonitor().mInputFocusRequestTimeMillis
                             >= getInputDispatchingTimeoutMillisLocked(
                                     targetWindowState.getActivityRecord());
-
-                    if (requestIsValid) {
-                        if (notifyWindowUnresponsive(focusToken, timeoutRecord)) {
-                            Slog.i(TAG_WM, "Blamed " + focusTarget.getWindowState().getName()
-                                    + " using pending focus request. Focused activity: "
-                                    + activity.getName());
-                            return;
-                        }
-                    }
                 }
 
-                Slog.i(TAG_WM, "ANR in " + activity.getName() + ".  Reason: "
-                        + timeoutRecord.mReason);
-                dumpAnrStateLocked(activity, null /* windowState */, timeoutRecord.mReason);
-                mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
+                if (!blamePendingFocusRequest) {
+                    Slog.i(TAG_WM, "ANR in " + activity.getName() + ".  Reason: "
+                            + timeoutRecord.mReason);
+                    dumpAnrStateLocked(activity, null /* windowState */, timeoutRecord.mReason);
+                    mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
+                }
             }
-            activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
+
+            if (blamePendingFocusRequest && notifyWindowUnresponsive(focusToken, timeoutRecord)) {
+                Slog.i(TAG_WM, "Blamed " + targetWindowState.getName()
+                        + " using pending focus request. Focused activity: "
+                        + activity.getName());
+            } else {
+                activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
+            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index fd6c974..b160af6a 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
                 return mService.getRecentTasks().createRecentTaskInfo(task,
-                        false /* stripExtras */);
+                        false /* stripExtras */, true /* getTasksAllowed */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 2ea6a3f..c2e87e6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1030,8 +1030,12 @@
     private void applyAnimations(ArraySet<ActivityRecord> openingApps,
             ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
             LayoutParams animLp, boolean voiceInteraction) {
+        final RecentsAnimationController rac = mService.getRecentsAnimationController();
         if (transit == WindowManager.TRANSIT_OLD_UNSET
                 || (openingApps.isEmpty() && closingApps.isEmpty())) {
+            if (rac != null) {
+                rac.sendTasksAppeared();
+            }
             return;
         }
 
@@ -1069,7 +1073,6 @@
                 voiceInteraction);
         applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                 voiceInteraction);
-        final RecentsAnimationController rac = mService.getRecentsAnimationController();
         if (rac != null) {
             rac.sendTasksAppeared();
         }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 1266db5..7d9ae87 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,10 +202,15 @@
         // target windows. But the windows still need to use sync transaction to keep the appearance
         // in previous rotation, so request a no-op sync to keep the state.
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
+                // Expect a screenshot layer will cover the non seamless windows.
+                continue;
+            }
             final WindowToken token = mTargetWindowTokens.keyAt(i);
             for (int j = token.getChildCount() - 1; j >= 0; j--) {
                 // TODO(b/234585256): The consumer should be handleFinishDrawing().
                 token.getChildAt(j).applyWithNextDraw(t -> {});
+                if (DEBUG) Slog.d(TAG, "Sync draw for " + token.getChildAt(j));
             }
         }
         mIsSyncDrawRequested = true;
@@ -483,7 +488,7 @@
             return false;
         }
         final Operation op = mTargetWindowTokens.get(w.mToken);
-        if (op == null) return false;
+        if (op == null || op.canDrawBeforeStartTransaction()) return false;
         if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
         if (op.mDrawTransaction == null) {
             if (w.isClientLocal()) {
@@ -548,5 +553,14 @@
         Operation(@Action int action) {
             mAction = action;
         }
+
+        /**
+         * Returns {@code true} if the corresponding window can draw its latest content before the
+         * start transaction of rotation transition is applied.
+         */
+        boolean canDrawBeforeStartTransaction() {
+            return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
+                    && mAction != ACTION_SEAMLESS;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index a3f5401..e977447 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -16,11 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -34,15 +37,20 @@
 import android.view.IWindowFocusObserver;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.window.BackAnimationAdapter;
 import android.window.BackNavigationInfo;
+import android.window.IBackAnimationFinishedCallback;
 import android.window.OnBackInvokedCallbackInfo;
 import android.window.ScreenCapture;
 import android.window.TaskSnapshot;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
 
+import java.util.ArrayList;
+
 /**
  * Controller to handle actions related to the back gesture on the server side.
  */
@@ -50,7 +58,13 @@
     private static final String TAG = "BackNavigationController";
     private WindowManagerService mWindowManagerService;
     private IWindowFocusObserver mFocusObserver;
+    private boolean mBackAnimationInProgress;
+    private boolean mShowWallpaper;
+    private Runnable mPendingAnimation;
 
+    // TODO (b/241808055) Find a appropriate time to remove during refactor
+    // Execute back animation with legacy transition system. Temporary flag for easier debugging.
+    static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
     /**
      * Returns true if the back predictability feature is enabled
      */
@@ -72,10 +86,9 @@
      */
     @VisibleForTesting
     @Nullable
-    BackNavigationInfo startBackNavigation(boolean requestAnimation,
-            IWindowFocusObserver observer) {
+    BackNavigationInfo startBackNavigation(
+            IWindowFocusObserver observer, BackAnimationAdapter adapter) {
         final WindowManagerService wmService = mWindowManagerService;
-        final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get();
         mFocusObserver = observer;
 
         int backType = BackNavigationInfo.TYPE_UNDEFINED;
@@ -95,18 +108,10 @@
         // currentActivity is the last child of currentTask.
         ActivityRecord prevActivity;
         WindowContainer<?> removedWindowContainer = null;
-        SurfaceControl animationLeashParent = null;
-        HardwareBuffer screenshotBuffer = null;
-        RemoteAnimationTarget topAppTarget = null;
         WindowState window;
 
-        int prevTaskId;
-        int prevUserId;
-        boolean prepareAnimation;
-
         BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
         synchronized (wmService.mGlobalLock) {
-            WindowConfiguration taskWindowConfiguration;
             WindowManagerInternal windowManagerInternal =
                     LocalServices.getService(WindowManagerInternal.class);
             IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken();
@@ -204,12 +209,13 @@
                 infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
                 final WindowState finalFocusedWindow = window;
                 infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
-                        onBackNavigationDone(result, finalFocusedWindow, finalFocusedWindow,
-                                BackNavigationInfo.TYPE_CALLBACK, null, null, false)));
+                        onBackNavigationDone(result, finalFocusedWindow,
+                                BackNavigationInfo.TYPE_CALLBACK)));
 
                 return infoBuilder.setType(backType).build();
             }
 
+            mBackAnimationInProgress = true;
             // We don't have an application callback, let's find the destination of the back gesture
             Task finalTask = currentTask;
             prevActivity = currentTask.getActivity(
@@ -230,6 +236,7 @@
                 // Our Task should bring back to home
                 removedWindowContainer = currentTask;
                 backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                mShowWallpaper = true;
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
@@ -242,12 +249,10 @@
                 } else {
                     backType = BackNavigationInfo.TYPE_CROSS_TASK;
                 }
+                mShowWallpaper = true;
             }
             infoBuilder.setType(backType);
 
-            prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
-            prevUserId = prevTask != null ? prevTask.mUserId : 0;
-
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s "
                             + "removedContainer:%s, backType=%s",
                     prevActivity != null ? prevActivity.mActivityComponent : null,
@@ -256,167 +261,275 @@
                     BackNavigationInfo.typeToString(backType));
 
             // For now, we only animate when going home.
-            prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
-                    && requestAnimation
-                    // Only create a new leash if no leash has been created.
-                    // Otherwise return null for animation target to avoid conflict.
-                    && !removedWindowContainer.hasCommittedReparentToAnimationLeash();
+            boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                    && adapter != null;
+
+            // Only prepare animation if no leash has been created (no animation is running).
+            // TODO(b/241808055): Cancel animation when preparing back animation.
+            if (prepareAnimation
+                    && (removedWindowContainer.hasCommittedReparentToAnimationLeash()
+                            || removedWindowContainer.mTransitionController.inTransition())) {
+                Slog.w(TAG, "Can't prepare back animation due to another animation is running.");
+                prepareAnimation = false;
+            }
 
             if (prepareAnimation) {
-                taskWindowConfiguration =
-                        currentTask.getTaskInfo().configuration.windowConfiguration;
-
-                infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration);
-                // Prepare a leash to animate the current top window
-                // TODO(b/220934562): Use surface animator to better manage animation conflicts.
-                SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash()
-                        .setName("BackPreview Leash for " + removedWindowContainer)
-                        .setHidden(false)
-                        .setBLASTLayer()
-                        .build();
-                removedWindowContainer.reparentSurfaceControl(tx, animLeash);
-                animationLeashParent = removedWindowContainer.getAnimationLeashParent();
-                topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer,
-                        currentActivity,
-                        currentTask, animLeash);
-                infoBuilder.setDepartingAnimationTarget(topAppTarget);
+                infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask));
+                prepareAnimationIfNeeded(currentTask, prevTask, prevActivity,
+                        removedWindowContainer, backType, adapter);
             }
-
-            //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is
-            // implemented. For now we simply have the mBackScreenshots hash map that dumbly
-            // saves the screenshots.
-            if (needsScreenshot(backType) && prevActivity != null
-                    && prevActivity.mActivityComponent != null) {
-                screenshotBuffer =
-                        getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
-            }
-
-            // Special handling for back to home animation
-            if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation
-                    && prevTask != null) {
-                currentTask.mBackGestureStarted = true;
-                // Make launcher show from behind by marking its top activity as visible and
-                // launch-behind to bump its visibility for the duration of the back gesture.
-                prevActivity = prevTask.getTopNonFinishingActivity();
-                if (prevActivity != null) {
-                    if (!prevActivity.mVisibleRequested) {
-                        prevActivity.setVisibility(true);
-                    }
-                    prevActivity.mLaunchTaskBehind = true;
-                    ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
-                            "Setting Activity.mLauncherTaskBehind to true. Activity=%s",
-                            prevActivity);
-                    prevActivity.mRootWindowContainer.ensureActivitiesVisible(
-                            null /* starting */, 0 /* configChanges */,
-                            false /* preserveWindows */);
-                }
-            }
+            infoBuilder.setPrepareRemoteAnimation(prepareAnimation);
         } // Release wm Lock
 
-        // Find a screenshot of the previous activity if we actually have an animation
-        if (topAppTarget != null && needsScreenshot(backType) && prevTask != null
-                && screenshotBuffer == null) {
-            SurfaceControl.Builder builder = new SurfaceControl.Builder()
-                    .setName("BackPreview Screenshot for " + prevActivity)
-                    .setParent(animationLeashParent)
-                    .setHidden(false)
-                    .setBLASTLayer();
-            infoBuilder.setScreenshotSurface(builder.build());
-            screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
-            infoBuilder.setScreenshotBuffer(screenshotBuffer);
-
-
-            // The Animation leash needs to be above the screenshot surface, but the animation leash
-            // needs to be added before to be in the synchronized block.
-            tx.setLayer(topAppTarget.leash, 1);
-        }
-
         WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
         if (finalRemovedWindowContainer != null) {
-            try {
-                currentActivity.token.linkToDeath(
-                        () -> resetSurfaces(finalRemovedWindowContainer), 0);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to link to death", e);
-                resetSurfaces(removedWindowContainer);
-                return null;
-            }
-
-            int finalBackType = backType;
-            final ActivityRecord finalprevActivity = prevActivity;
-            final Task finalTask = currentTask;
+            final int finalBackType = backType;
             final WindowState finalFocusedWindow = window;
             RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
-                    result, finalFocusedWindow, finalRemovedWindowContainer, finalBackType,
-                    finalTask, finalprevActivity, prepareAnimation));
+                    result, finalFocusedWindow, finalBackType));
             infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
         }
 
-        tx.apply();
         return infoBuilder.build();
     }
 
+    private static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) {
+        if (windowContainer == null || windowContainer.mRemoteToken == null) {
+            return null;
+        }
+        return windowContainer.mRemoteToken.toWindowContainerToken();
+    }
+
+    private void prepareAnimationIfNeeded(Task currentTask,
+            Task prevTask, ActivityRecord prevActivity, WindowContainer<?> removedWindowContainer,
+            int backType, BackAnimationAdapter adapter) {
+        final ArrayList<SurfaceControl> leashes = new ArrayList<>();
+        final SurfaceControl.Transaction startedTransaction = currentTask.getPendingTransaction();
+        final SurfaceControl.Transaction finishedTransaction = new SurfaceControl.Transaction();
+        // Prepare a leash to animate for the departing window
+        final SurfaceControl animLeash = currentTask.makeAnimationLeash()
+                .setName("BackPreview Leash for " + currentTask)
+                .setHidden(false)
+                .build();
+        removedWindowContainer.reparentSurfaceControl(startedTransaction, animLeash);
+
+        final RemoteAnimationTarget topAppTarget = createRemoteAnimationTargetLocked(
+                currentTask, animLeash, MODE_CLOSING);
+
+        // reset leash after animation finished.
+        leashes.add(animLeash);
+        removedWindowContainer.reparentSurfaceControl(finishedTransaction,
+                removedWindowContainer.getParentSurfaceControl());
+
+        // Prepare a leash to animate for the entering window.
+        RemoteAnimationTarget behindAppTarget = null;
+        if (needsScreenshot(backType)) {
+            HardwareBuffer screenshotBuffer = null;
+            switch(backType) {
+                case BackNavigationInfo.TYPE_CROSS_TASK:
+                    int prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
+                    int prevUserId = prevTask != null ? prevTask.mUserId : 0;
+                    screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
+                    break;
+                case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
+                    //TODO(207481538) Remove once the infrastructure to support per-activity
+                    // screenshot is implemented. For now we simply have the mBackScreenshots hash
+                    // map that dumbly saves the screenshots.
+                    if (prevActivity != null
+                            && prevActivity.mActivityComponent != null) {
+                        screenshotBuffer =
+                                getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
+                    }
+                    break;
+            }
+
+            // Find a screenshot of the previous activity if we actually have an animation
+            SurfaceControl animationLeashParent = removedWindowContainer.getAnimationLeashParent();
+            if (screenshotBuffer != null) {
+                final SurfaceControl screenshotSurface = new SurfaceControl.Builder()
+                        .setName("BackPreview Screenshot for " + prevActivity)
+                        .setHidden(false)
+                        .setParent(animationLeashParent)
+                        .setBLASTLayer()
+                        .build();
+                startedTransaction.setBuffer(screenshotSurface, screenshotBuffer);
+
+                // The Animation leash needs to be above the screenshot surface, but the animation
+                // leash needs to be added before to be in the synchronized block.
+                startedTransaction.setLayer(topAppTarget.leash, 1);
+
+                behindAppTarget = createRemoteAnimationTargetLocked(
+                        prevTask, screenshotSurface, MODE_OPENING);
+
+                // reset leash after animation finished.
+                leashes.add(screenshotSurface);
+            }
+        } else if (prevTask != null) {
+            if (!ENABLE_SHELL_TRANSITIONS) {
+                // Special handling for preventing next transition.
+                currentTask.mBackGestureStarted = true;
+            }
+            prevActivity = prevTask.getTopNonFinishingActivity();
+            if (prevActivity != null) {
+                // Make previous task show from behind by marking its top activity as visible
+                // and launch-behind to bump its visibility for the duration of the back gesture.
+                setLaunchBehind(prevActivity);
+
+                final SurfaceControl leash = prevActivity.makeAnimationLeash()
+                        .setName("BackPreview Leash for " + prevActivity)
+                        .setHidden(false)
+                        .build();
+                prevActivity.reparentSurfaceControl(startedTransaction, leash);
+                behindAppTarget = createRemoteAnimationTargetLocked(
+                        prevTask, leash, MODE_OPENING);
+
+                // reset leash after animation finished.
+                leashes.add(leash);
+                prevActivity.reparentSurfaceControl(finishedTransaction,
+                        prevActivity.getParentSurfaceControl());
+            }
+        }
+
+        if (mShowWallpaper) {
+            currentTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+            // TODO(b/241808055): If the current animation need to show wallpaper and animate the
+            //  wallpaper, start the wallpaper animation to collect wallpaper target and deliver it
+            //  to the back animation controller.
+        }
+
+        final RemoteAnimationTarget[] targets = (behindAppTarget == null)
+                ? new RemoteAnimationTarget[] {topAppTarget}
+                : new RemoteAnimationTarget[] {topAppTarget, behindAppTarget};
+
+        final ActivityRecord finalPrevActivity = prevActivity;
+        final IBackAnimationFinishedCallback callback =
+                new IBackAnimationFinishedCallback.Stub() {
+                    @Override
+                    public void onAnimationFinished(boolean triggerBack) {
+                        for (SurfaceControl sc: leashes) {
+                            finishedTransaction.remove(sc);
+                        }
+
+                        synchronized (mWindowManagerService.mGlobalLock) {
+                            if (ENABLE_SHELL_TRANSITIONS) {
+                                if (!triggerBack) {
+                                    if (!needsScreenshot(backType)) {
+                                        restoreLaunchBehind(finalPrevActivity);
+                                    }
+                                }
+                            } else {
+                                if (triggerBack) {
+                                    final SurfaceControl surfaceControl =
+                                            removedWindowContainer.getSurfaceControl();
+                                    if (surfaceControl != null && surfaceControl.isValid()) {
+                                        // When going back to home, hide the task surface before it
+                                        // re-parented to avoid flicker.
+                                        finishedTransaction.hide(surfaceControl);
+                                    }
+                                } else {
+                                    currentTask.mBackGestureStarted = false;
+                                    if (!needsScreenshot(backType)) {
+                                        restoreLaunchBehind(finalPrevActivity);
+                                    }
+                                }
+                            }
+                        }
+                        finishedTransaction.apply();
+                    }
+                };
+
+        scheduleAnimationLocked(backType, targets, adapter, callback);
+    }
+
     @NonNull
     private static RemoteAnimationTarget createRemoteAnimationTargetLocked(
-            WindowContainer<?> removedWindowContainer,
-            ActivityRecord activityRecord, Task task, SurfaceControl animLeash) {
+            Task task, SurfaceControl animLeash, int mode) {
+        ActivityRecord topApp = task.getTopRealVisibleActivity();
+        if (topApp == null) {
+            topApp = task.getTopNonFinishingActivity();
+        }
+
+        final WindowState mainWindow = topApp != null
+                ? topApp.findMainWindow()
+                : null;
+        int windowType = INVALID_WINDOW_TYPE;
+        if (mainWindow != null) {
+            windowType = mainWindow.getWindowType();
+        }
+
+        Rect bounds = new Rect(task.getBounds());
+        Rect localBounds = new Rect(bounds);
+        Point tmpPos = new Point();
+        task.getRelativePosition(tmpPos);
+        localBounds.offsetTo(tmpPos.x, tmpPos.y);
+
         return new RemoteAnimationTarget(
                 task.mTaskId,
-                RemoteAnimationTarget.MODE_CLOSING,
+                mode,
                 animLeash,
                 false /* isTransluscent */,
                 new Rect() /* clipRect */,
                 new Rect() /* contentInsets */,
-                activityRecord.getPrefixOrderIndex(),
-                new Point(0, 0) /* position */,
-                new Rect() /* localBounds */,
-                new Rect() /* screenSpaceBounds */,
-                removedWindowContainer.getWindowConfiguration(),
+                task.getPrefixOrderIndex(),
+                tmpPos /* position */,
+                localBounds /* localBounds */,
+                bounds /* screenSpaceBounds */,
+                task.getWindowConfiguration(),
                 true /* isNotInRecent */,
                 null,
                 null,
                 task.getTaskInfo(),
                 false,
-                activityRecord.windowType);
+                windowType);
     }
 
-    private void onBackNavigationDone(
-            Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
-            int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
-            boolean prepareAnimation) {
-        SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
+    @VisibleForTesting
+    void scheduleAnimationLocked(@BackNavigationInfo.BackTargetType int type,
+            RemoteAnimationTarget[] targets, BackAnimationAdapter backAnimationAdapter,
+            IBackAnimationFinishedCallback callback) {
+        mPendingAnimation = () -> {
+            try {
+                backAnimationAdapter.getRunner().onAnimationStart(type,
+                        targets, null, null, callback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        };
+        mWindowManagerService.mWindowPlacerLocked.requestTraversal();
+    }
+
+    void checkAnimationReady(WallpaperController wallpaperController) {
+        if (!mBackAnimationInProgress) {
+            return;
+        }
+
+        final boolean wallpaperReady = !mShowWallpaper
+                || (wallpaperController.getWallpaperTarget() != null
+                && wallpaperController.wallpaperTransitionReady());
+        if (wallpaperReady && mPendingAnimation != null) {
+            startAnimation();
+        }
+    }
+
+    void startAnimation() {
+        if (mPendingAnimation != null) {
+            mPendingAnimation.run();
+            mPendingAnimation = null;
+        }
+    }
+
+    private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) {
         boolean triggerBack = result != null && result.getBoolean(
                 BackNavigationInfo.KEY_TRIGGER_BACK);
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
-                + "task=%s, prevActivity=%s", backType, task, prevActivity);
-
-        if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) {
-            if (triggerBack) {
-                if (surfaceControl != null && surfaceControl.isValid()) {
-                    // When going back to home, hide the task surface before it is re-parented to
-                    // avoid flicker.
-                    SurfaceControl.Transaction t = windowContainer.getSyncTransaction();
-                    t.hide(surfaceControl);
-                    t.apply();
-                }
-            }
-            if (prevActivity != null && !triggerBack) {
-                // Restore the launch-behind state.
-                task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token);
-                prevActivity.mLaunchTaskBehind = false;
-                ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
-                        "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
-                        prevActivity);
-            }
-        } else if (task != null) {
-            task.mBackGestureStarted = false;
-        }
-        resetSurfaces(windowContainer);
+                + "triggerBack=%b", backType, triggerBack);
 
         if (mFocusObserver != null) {
             focusedWindow.unregisterFocusObserver(mFocusObserver);
             mFocusObserver = null;
         }
+        mBackAnimationInProgress = false;
+        mShowWallpaper = false;
     }
 
     private HardwareBuffer getActivitySnapshot(@NonNull Task task,
@@ -450,20 +563,54 @@
         return true;
     }
 
-    private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) {
-        synchronized (windowContainer.mWmService.mGlobalLock) {
-            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces");
-            SurfaceControl.Transaction tx = windowContainer.getSyncTransaction();
-            SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
-            if (surfaceControl != null) {
-                tx.reparent(surfaceControl,
-                        windowContainer.getParent().getSurfaceControl());
-                tx.apply();
-            }
-        }
-    }
-
     void setWindowManager(WindowManagerService wm) {
         mWindowManagerService = wm;
     }
+
+    private void setLaunchBehind(ActivityRecord activity) {
+        if (activity == null) {
+            return;
+        }
+        if (!activity.mVisibleRequested) {
+            activity.setVisibility(true);
+        }
+        activity.mLaunchTaskBehind = true;
+
+        // Handle fixed rotation launching app.
+        final DisplayContent dc = activity.mDisplayContent;
+        dc.rotateInDifferentOrientationIfNeeded(activity);
+        if (activity.hasFixedRotationTransform()) {
+            // Set the record so we can recognize it to continue to update display orientation
+            // if the previous activity becomes the top later.
+            dc.setFixedRotationLaunchingApp(activity,
+                    activity.getWindowConfiguration().getRotation());
+        }
+
+        ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+                "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
+        activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+                0 /* configChanges */, false /* preserveWindows */, true);
+    }
+
+    private void restoreLaunchBehind(ActivityRecord activity) {
+        if (activity == null) {
+            return;
+        }
+
+        activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+
+        // Restore the launch-behind state.
+        activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
+        activity.mLaunchTaskBehind = false;
+        ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+                "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
+                activity);
+    }
+
+    boolean isWallpaperVisible(WindowState w) {
+        if (mBackAnimationInProgress && w.isFocused()) {
+            return mShowWallpaper;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index acbf1a4..6e23ed9 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -391,8 +391,7 @@
      * </p>
      */
     private void handleStartRecordingFailed() {
-        final boolean shouldExitTaskRecording = mContentRecordingSession != null
-                && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+        final boolean shouldExitTaskRecording = isRecordingContentTask();
         clearContentRecordingSession();
         if (shouldExitTaskRecording) {
             // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,9 +477,10 @@
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                 "Recorded task is removed, so stop recording on display %d",
                 mDisplayContent.getDisplayId());
-        Task recordedTask = mRecordedWindowContainer.asTask();
-        if (recordedTask == null
-                || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+
+        Task recordedTask = mRecordedWindowContainer != null
+                ? mRecordedWindowContainer.asTask() : null;
+        if (recordedTask == null || !isRecordingContentTask()) {
             return;
         }
         recordedTask.unregisterWindowContainerListener(this);
@@ -504,4 +504,9 @@
     @VisibleForTesting interface MediaProjectionManagerWrapper {
         void stopActiveProjection();
     }
+
+    private boolean isRecordingContentTask() {
+        return mContentRecordingSession != null
+                && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2809307..6c5222b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -196,6 +196,7 @@
 import android.util.DisplayMetrics;
 import android.util.DisplayUtils;
 import android.util.IntArray;
+import android.util.Pair;
 import android.util.RotationUtils;
 import android.util.Size;
 import android.util.Slog;
@@ -241,7 +242,6 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -431,6 +431,8 @@
     private final DisplayRotation mDisplayRotation;
     DisplayFrames mDisplayFrames;
 
+    private boolean mInTouchMode;
+
     private final RemoteCallbackList<ISystemGestureExclusionListener>
             mSystemGestureExclusionListeners = new RemoteCallbackList<>();
     private final Region mSystemGestureExclusion = new Region();
@@ -1155,6 +1157,12 @@
 
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+
+        // Sets the initial touch mode state.
+        mInTouchMode = mWmService.mContext.getResources().getBoolean(
+                R.bool.config_defaultInTouchMode);
+        mWmService.mInputManager.setInTouchMode(mInTouchMode, mWmService.MY_PID, mWmService.MY_UID,
+                /* hasPermission= */ true, mDisplayId);
     }
 
     private void beginHoldScreenUpdate() {
@@ -1265,6 +1273,18 @@
         return mWmService.mDisplayReady && mDisplayReady;
     }
 
+    boolean setInTouchMode(boolean inTouchMode) {
+        if (mInTouchMode == inTouchMode) {
+            return false;
+        }
+        mInTouchMode = inTouchMode;
+        return true;
+    }
+
+    boolean isInTouchMode() {
+        return mInTouchMode;
+    }
+
     int getDisplayId() {
         return mDisplayId;
     }
@@ -3075,18 +3095,13 @@
      */
     boolean pointWithinAppWindow(int x, int y) {
         final int[] targetWindowType = {-1};
-        final PooledConsumer fn = PooledLambda.obtainConsumer((w, nonArg) -> {
-            if (targetWindowType[0] != -1) {
-                return;
-            }
-
+        forAllWindows(w -> {
             if (w.isOnScreen() && w.isVisible() && w.getFrame().contains(x, y)) {
                 targetWindowType[0] = w.mAttrs.type;
-                return;
+                return true;
             }
-        }, PooledLambda.__(WindowState.class), mTmpRect);
-        forAllWindows(fn, true /* traverseTopToBottom */);
-        fn.recycle();
+            return false;
+        }, true /* traverseTopToBottom */);
         return FIRST_APPLICATION_WINDOW <= targetWindowType[0]
                 && targetWindowType[0] <= LAST_APPLICATION_WINDOW;
     }
@@ -3112,11 +3127,7 @@
             mTmpRect.setEmpty();
             mTmpRect2.setEmpty();
 
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    DisplayContent::processTaskForTouchExcludeRegion, this,
-                    PooledLambda.__(Task.class), focusedTask, delta);
-            forAllTasks(c);
-            c.recycle();
+            forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
 
             // If we removed the focused task above, add it back and only leave its
             // outside touch area in the exclusion. TapDetector is not interested in
@@ -4164,7 +4175,7 @@
         }
 
         ProtoLog.i(WM_DEBUG_IME, "setInputMethodTarget %s", target);
-        final boolean layeringTargetChanged = target != mImeLayeringTarget;
+        boolean shouldUpdateImeParent = target != mImeLayeringTarget;
         mImeLayeringTarget = target;
 
         // 1. Reparent the IME container window to the target root DA to get the correct bounds and
@@ -4172,10 +4183,12 @@
         // is not organized (see FEATURE_IME and updateImeParent).
         if (target != null && !mImeWindowsContainer.isOrganized()) {
             RootDisplayArea targetRoot = target.getRootDisplayArea();
-            if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) {
-                // Reposition the IME container to the target root to get the correct bounds and
-                // config.
-                targetRoot.placeImeContainer(mImeWindowsContainer);
+            if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()
+                    // Try reparent the IME container to the target root to get the bounds and
+                    // config that match the target window.
+                    && targetRoot.placeImeContainer(mImeWindowsContainer)) {
+                // Update the IME surface parent since the IME container window has been reparented.
+                shouldUpdateImeParent = true;
                 // Directly hide the IME window so it doesn't flash immediately after reparenting.
                 // InsetsController will make IME visible again before animating it.
                 if (mInputMethodWindow != null) {
@@ -4192,7 +4205,7 @@
         // 4. Update the IME control target to apply any inset change and animation.
         // 5. Reparent the IME container surface to either the input target app, or the IME window
         // parent.
-        updateImeControlTarget(layeringTargetChanged);
+        updateImeControlTarget(shouldUpdateImeParent);
     }
 
     @VisibleForTesting
@@ -4545,6 +4558,7 @@
             return;
         }
         pw.println("  Display #" + mDisplayId);
+        pw.println("    mInTouchMode=" + mInTouchMode);
         final Iterator<WindowToken> it = mTokenMap.values().iterator();
         while (it.hasNext()) {
             final WindowToken token = it.next();
@@ -4898,20 +4912,19 @@
             return null;
         }
 
-        final ScreenRotationAnimation screenRotationAnimation =
-                mWmService.mRoot.getDisplayContent(DEFAULT_DISPLAY).getRotationAnimation();
-        final boolean inRotation = screenRotationAnimation != null &&
-                screenRotationAnimation.isAnimating();
-        if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
+        Pair<ScreenCapture.ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
 
-        // Send invalid rect and no width and height since it will screenshot the entire display.
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        final ScreenCapture.DisplayCaptureArgs captureArgs =
-                new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
-                        .setUseIdentityTransform(inRotation)
-                        .build();
+        getBounds(mTmpRect);
+        mTmpRect.offsetTo(0, 0);
+        ScreenCapture.LayerCaptureArgs args =
+                new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
+                        .setSourceCrop(mTmpRect).build();
+
+        ScreenCapture.captureLayers(args, syncScreenCapture.first);
+
         final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                ScreenCapture.captureDisplay(captureArgs);
+                syncScreenCapture.second.get();
         final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (bitmap == null) {
             Slog.w(TAG_WM, "Failed to take screenshot");
@@ -6185,17 +6198,10 @@
     /** Update and get all UIDs that are present on the display and have access to it. */
     IntArray getPresentUIDs() {
         mDisplayAccessUIDs.clear();
-        final PooledConsumer c = PooledLambda.obtainConsumer(DisplayContent::addActivityUid,
-                PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
-        mDisplayContent.forAllActivities(c);
-        c.recycle();
+        mDisplayContent.forAllActivities(r -> { mDisplayAccessUIDs.add(r.getUid()); });
         return mDisplayAccessUIDs;
     }
 
-    private static void addActivityUid(ActivityRecord r, IntArray uids) {
-        uids.add(r.getUid());
-    }
-
     @VisibleForTesting
     boolean shouldDestroyContentOnRemove() {
         return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
@@ -6801,4 +6807,11 @@
     DisplayContent asDisplayContent() {
         return this;
     }
+
+    @Override
+    @Surface.Rotation
+    int getRelativeDisplayRotation() {
+        // Display is the root, so it's not rotated relative to anything.
+        return Surface.ROTATION_0;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e780606..ac61cb9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2167,10 +2167,7 @@
      * If the decor insets changes, the display configuration may be affected. The caller should
      * call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
      */
-    boolean updateDecorInsetsInfoIfNeeded(WindowState win) {
-        if (!win.providesNonDecorInsets()) {
-            return false;
-        }
+    boolean updateDecorInsetsInfo() {
         final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
         final int rotation = displayFrames.mRotation;
         final int dw = displayFrames.mWidth;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 25ff023..0b28ba2 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -228,8 +228,8 @@
                 SurfaceControl dragSurface = null;
                 if (!mDragResult && (ws.mSession.mPid == mPid)) {
                     // Report unconsumed drop location back to the app that started the drag.
-                    x = mCurrentX;
-                    y = mCurrentY;
+                    x = ws.translateToWindowX(mCurrentX);
+                    y = ws.translateToWindowY(mCurrentY);
                     if (relinquishDragSurfaceToDragSource()) {
                         // If requested (and allowed), report the drag surface back to the app
                         // starting the drag to handle the return animation
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index dcc16eb..d54f77a 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -19,17 +19,16 @@
 
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
-import static com.android.server.wm.WindowContainerProto.IDENTIFIER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateProto.IDENTIFIER;
 
 import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.proto.ProtoOutputStream;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
                 continue;
             }
 
-            res.add(createRecentTaskInfo(task, true /* stripExtras */));
+            res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
         }
         return res;
     }
@@ -1895,7 +1895,8 @@
     /**
      * Creates a new RecentTaskInfo from a Task.
      */
-    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+            boolean getTasksAllowed) {
         final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         // If the recent Task is detached, we consider it will be re-attached to the default
         // TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
         rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+        if (!getTasksAllowed) {
+            Task.trimIneffectiveInfo(tr, rti);
+        }
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 7bb57d8..bffab7a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -62,8 +62,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -116,7 +114,7 @@
     private boolean mWillFinishToHome = false;
     private final Runnable mFailsafeRunnable = this::onFailsafe;
 
-    // The recents component app token that is shown behind the visibile tasks
+    // The recents component app token that is shown behind the visible tasks
     private ActivityRecord mTargetActivityRecord;
     private DisplayContent mDisplayContent;
     private int mTargetActivityType;
@@ -403,11 +401,11 @@
         final Task targetRootTask = mDisplayContent.getDefaultTaskDisplayArea()
                 .getRootTask(WINDOWING_MODE_UNDEFINED, targetActivityType);
         if (targetRootTask != null) {
-            final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
-	            { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
-                    visibleTasks);
-            targetRootTask.forAllLeafTasks(c, true /* traverseTopToBottom */);
-            c.recycle();
+            targetRootTask.forAllLeafTasks(t -> {
+                if (!visibleTasks.contains(t)) {
+                    visibleTasks.add(t);
+                }
+            }, true /* traverseTopToBottom */);
         }
 
         final int taskCount = visibleTasks.size();
@@ -456,6 +454,22 @@
         }
     }
 
+    /**
+     * Return whether the given window should still be considered interesting for the all-drawn
+     * state.  This is only interesting for the target app, which may have child windows that are
+     * not actually visible and should not be considered interesting and waited upon.
+     */
+    protected boolean isInterestingForAllDrawn(WindowState window) {
+        if (isTargetApp(window.getActivityRecord())) {
+            if (window.getWindowType() != TYPE_BASE_APPLICATION
+                    && window.getAttrs().alpha == 0f) {
+                // If there is a cihld window that is alpha 0, then ignore that window
+                return false;
+            }
+        }
+        // By default all windows are still interesting for all drawn purposes
+        return true;
+    }
 
     /**
      * Whether a task should be filtered from the recents animation. This can be true for tasks
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index d94bb9e..092adc3 100644
--- a/services/core/java/com/android/server/wm/RootDisplayArea.java
+++ b/services/core/java/com/android/server/wm/RootDisplayArea.java
@@ -22,8 +22,10 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
+import android.util.Slog;
 
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -76,8 +78,10 @@
     /**
      * Places the IME container below this root, so that it's bounds and config will be updated to
      * match the root.
+     *
+     * @return {@code true} if the IME container is reparented to this root.
      */
-    void placeImeContainer(DisplayArea.Tokens imeContainer) {
+    boolean placeImeContainer(DisplayArea.Tokens imeContainer) {
         final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea();
 
         List<Feature> features = mFeatures;
@@ -94,11 +98,23 @@
                 previousRoot.updateImeContainerForLayers(null /* imeContainer */);
                 imeContainer.reparent(imeDisplayAreas.get(0), POSITION_TOP);
                 updateImeContainerForLayers(imeContainer);
-                return;
+                return true;
             }
         }
-        throw new IllegalStateException(
-                "There is no FEATURE_IME_PLACEHOLDER in this root to place the IME container");
+
+        // Some device UX may not have the need to update the IME bounds and position for IME target
+        // in a child DisplayArea, so instead of throwing exception, we just allow the IME container
+        // to remain in its previous root.
+        if (!isDescendantOf(previousRoot)) {
+            // When this RootDisplayArea is a descendant of the current RootDisplayArea, it will be
+            // at the APPLICATION_LAYER, and the IME container will always be on top and have bounds
+            // equal or larger than the input target.
+            // If it is not a descendant, the DisplayAreaPolicy owner should make sure the IME is
+            // working correctly. Print a warning in case they are not.
+            Slog.w(TAG_WM, "The IME target is not in the same root as the IME container, but there "
+                    + "is no DisplayArea of FEATURE_IME_PLACEHOLDER in the target RootDisplayArea");
+        }
+        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index f66ba38..38c7595 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -837,6 +837,11 @@
         if (recentsAnimationController != null) {
             recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
         }
+        final BackNavigationController backNavigationController =
+                mWmService.mAtmService.mBackNavigationController;
+        if (backNavigationController != null) {
+            backNavigationController.checkAnimationReady(defaultDisplay.mWallpaperController);
+        }
 
         for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
@@ -1799,10 +1804,6 @@
         return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedActivity);
     }
 
-    boolean hasResumedActivity(int uid) {
-        return forAllActivities(ar -> ar.isState(RESUMED) && ar.getUid() == uid);
-    }
-
     boolean isTopDisplayFocusedRootTask(Task task) {
         return task != null && task == getTopDisplayFocusedRootTask();
     }
@@ -3106,9 +3107,8 @@
     }
 
     void finishVoiceTask(IVoiceInteractionSession session) {
-        forAllRootTasks(rootTask -> {
-            rootTask.finishVoiceTask(session);
-        });
+        final IBinder binder = session.asBinder();
+        forAllLeafTasks(t -> t.finishIfVoiceTask(binder), true /* traverseTopToBottom */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0..9c85bc0 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -23,18 +23,16 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.TreeSet;
+import java.util.function.Consumer;
 
 /**
  * Class for resolving the set of running tasks in the system.
  */
-class RunningTasks {
+class RunningTasks implements Consumer<Task> {
 
     static final int FLAG_FILTER_ONLY_VISIBLE_RECENTS = 1;
     static final int FLAG_ALLOWED = 1 << 1;
@@ -61,7 +59,7 @@
     private boolean mKeepIntentExtra;
 
     void getTasks(int maxNum, List<RunningTaskInfo> list, int flags, RecentTasks recentTasks,
-            WindowContainer root, int callingUid, ArraySet<Integer> profileIds) {
+            WindowContainer<?> root, int callingUid, ArraySet<Integer> profileIds) {
         // Return early if there are no tasks to fetch
         if (maxNum <= 0) {
             return;
@@ -79,10 +77,7 @@
         mRecentTasks = recentTasks;
         mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
 
-        final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this,
-                PooledLambda.__(Task.class));
-        root.forAllLeafTasks(c, false);
-        c.recycle();
+        root.forAllLeafTasks(this, false /* traverseTopToBottom */);
 
         // Take the first {@param maxNum} tasks and create running task infos for them
         final Iterator<Task> iter = mTmpSortedSet.iterator();
@@ -97,7 +92,8 @@
         }
     }
 
-    private void processTask(Task task) {
+    @Override
+    public void accept(Task task) {
         if (task.getTopNonFinishingActivity() == null) {
             // Skip if there are no activities in the task
             return;
@@ -142,6 +138,10 @@
         task.fillTaskInfo(rti, !mKeepIntentExtra);
         // Fill in some deprecated values
         rti.id = rti.taskId;
+
+        if (!mAllowed) {
+            Task.trimIneffectiveInfo(task, rti);
+        }
         return rti;
     }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b9739f03..9660fe2 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -291,17 +291,11 @@
     public void finishDrawing(IWindow window,
             @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
         if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
+        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);
+        }
         mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
-    }
-
-    @Override
-    public void setInTouchMode(boolean mode) {
-        mService.setInTouchMode(mode);
-    }
-
-    @Override
-    public boolean getInTouchMode() {
-        return mService.getInTouchMode();
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 837045c..5bd141a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -186,6 +186,7 @@
 import android.window.ITaskOrganizer;
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.StartingWindowInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -195,7 +196,6 @@
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.Watchdog;
@@ -1187,11 +1187,7 @@
         if (oldParent != null) {
             final Task oldParentTask = oldParent.asTask();
             if (oldParentTask != null) {
-                final PooledConsumer c = PooledLambda.obtainConsumer(
-                        Task::cleanUpActivityReferences, oldParentTask,
-                        PooledLambda.__(ActivityRecord.class));
-                forAllActivities(c);
-                c.recycle();
+                forAllActivities(oldParentTask::cleanUpActivityReferences);
             }
 
             if (oldParent.inPinnedWindowingMode()
@@ -2370,10 +2366,7 @@
 
     int getDescendantTaskCount() {
         final int[] currentCount = {0};
-        final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
-                PooledLambda.__(Task.class), currentCount);
-        forAllLeafTasks(c, false /* traverseTopToBottom */);
-        c.recycle();
+        forAllLeafTasks(t -> currentCount[0]++, false /* traverseTopToBottom */);
         return currentCount[0];
     }
 
@@ -2679,6 +2672,7 @@
         if (isRootTask()) {
             updateSurfaceBounds();
         }
+        sendTaskFragmentParentInfoChangedIfNeeded();
     }
 
     boolean isResizeable() {
@@ -2781,10 +2775,7 @@
                 && displayContent.mDividerControllerLocked.isResizing();
         if (inFreeformWindowingMode()) {
             boolean[] foundTop = { false };
-            final PooledConsumer c = PooledLambda.obtainConsumer(Task::getMaxVisibleBounds,
-                    PooledLambda.__(ActivityRecord.class), out, foundTop);
-            forAllActivities(c);
-            c.recycle();
+            forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
             if (foundTop[0]) {
                 return;
             }
@@ -2914,7 +2905,7 @@
     @Override
     boolean showToCurrentUser() {
         return mForceShowForAllUsers || showForAllUsers()
-                || mWmService.isCurrentProfile(getTopMostTask().mUserId);
+                || mWmService.isUserVisible(getTopMostTask().mUserId);
     }
 
     void setForceShowForAllUsers(boolean forceShowForAllUsers) {
@@ -3320,8 +3311,9 @@
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                         "applyAnimationUnchecked, control: %s, task: %s, transit: %s",
                         control, asTask(), AppTransition.appTransitionOldToString(transit));
+                final int size = sources != null ? sources.size() : 0;
                 control.addTaskToTargets(this, (type, anim) -> {
-                    for (int i = 0; i < sources.size(); ++i) {
+                    for (int i = 0; i < size; ++i) {
                         sources.get(i).onAnimationFinished(type, anim);
                     }
                 });
@@ -3439,6 +3431,27 @@
         info.isSleeping = shouldSleepActivities();
     }
 
+    /**
+     * Removes the activity info if the activity belongs to a different uid, which is
+     * different from the app that hosts the task.
+     */
+    static void trimIneffectiveInfo(Task task, TaskInfo info) {
+        final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+                false /* traverseTopToBottom */);
+        final int baseActivityUid =
+                baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+        if (info.topActivityInfo != null
+                && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+            info.topActivity = null;
+            info.topActivityInfo = null;
+        }
+
+        if (task.effectiveUid != baseActivityUid) {
+            info.baseActivity = null;
+        }
+    }
+
     @Nullable PictureInPictureParams getPictureInPictureParams() {
         final Task topTask = getTopMostTask();
         if (topTask == null) return null;
@@ -3513,6 +3526,33 @@
         return info;
     }
 
+    /**
+     * Returns the {@link TaskFragmentParentInfo} which will send to the client
+     * {@link android.window.TaskFragmentOrganizer}
+     */
+    TaskFragmentParentInfo getTaskFragmentParentInfo() {
+        return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+    }
+
+    @Override
+    void onActivityVisibleRequestedChanged() {
+        if (mVisibleRequested != isVisibleRequested()) {
+            sendTaskFragmentParentInfoChangedIfNeeded();
+        }
+    }
+
+    void sendTaskFragmentParentInfoChangedIfNeeded() {
+        if (!isLeafTask()) {
+            // Only send parent info changed event for leaf task.
+            return;
+        }
+        final TaskFragment childOrganizedTf =
+                getTaskFragment(TaskFragment::isOrganizedTaskFragment);
+        if (childOrganizedTf != null) {
+            childOrganizedTf.sendTaskFragmentParentInfoChanged();
+        }
+    }
+
     boolean isTaskId(int taskId) {
         return mTaskId == taskId;
     }
@@ -4319,14 +4359,6 @@
         }
     }
 
-
-    void setActivityWindowingMode(int windowingMode) {
-        PooledConsumer c = PooledLambda.obtainConsumer(ActivityRecord::setWindowingMode,
-                PooledLambda.__(ActivityRecord.class), windowingMode);
-        forAllActivities(c);
-        c.recycle();
-    }
-
     /**
      * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
      * @return Whether the force hidden state changed
@@ -5179,26 +5211,19 @@
         return finishedTask;
     }
 
-    void finishVoiceTask(IVoiceInteractionSession session) {
-        final PooledConsumer c = PooledLambda.obtainConsumer(Task::finishIfVoiceTask,
-                PooledLambda.__(Task.class), session.asBinder());
-        forAllLeafTasks(c, true /* traverseTopToBottom */);
-        c.recycle();
-    }
-
-    private static void finishIfVoiceTask(Task tr, IBinder binder) {
-        if (tr.voiceSession != null && tr.voiceSession.asBinder() == binder) {
-            tr.forAllActivities((r) -> {
+    void finishIfVoiceTask(IBinder binder) {
+        if (voiceSession != null && voiceSession.asBinder() == binder) {
+            forAllActivities((r) -> {
                 if (r.finishing) return;
                 r.finishIfPossible("finish-voice", false /* oomAdj */);
-                tr.mAtmService.updateOomAdj();
+                mAtmService.updateOomAdj();
             });
         } else {
             // Check if any of the activities are using voice
             final PooledPredicate f = PooledLambda.obtainPredicate(
                     Task::finishIfVoiceActivity, PooledLambda.__(ActivityRecord.class),
                     binder);
-            tr.forAllActivities(f);
+            forAllActivities(f);
             f.recycle();
         }
     }
@@ -5274,8 +5299,9 @@
         return false;
     }
 
-    boolean navigateUpTo(ActivityRecord srec, Intent destIntent, NeededUriGrants destGrants,
-            int resultCode, Intent resultData, NeededUriGrants resultGrants) {
+    boolean navigateUpTo(ActivityRecord srec, Intent destIntent, String resolvedType,
+            NeededUriGrants destGrants, int resultCode, Intent resultData,
+            NeededUriGrants resultGrants) {
         if (!srec.attachedToProcess()) {
             // Nothing to do if the caller is not attached, because this method should be called
             // from an alive activity.
@@ -5344,32 +5370,26 @@
 
         if (parent != null && foundParentInTask) {
             final int callingUid = srec.info.applicationInfo.uid;
-            try {
-                ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
-                        destIntent.getComponent(), ActivityManagerService.STOCK_PM_FLAGS,
-                        srec.mUserId);
-                // TODO(b/64750076): Check if calling pid should really be -1.
-                final int res = mAtmService.getActivityStartController()
-                        .obtainStarter(destIntent, "navigateUpTo")
-                        .setCaller(srec.app.getThread())
-                        .setActivityInfo(aInfo)
-                        .setResultTo(parent.token)
-                        .setIntentGrants(destGrants)
-                        .setCallingPid(-1)
-                        .setCallingUid(callingUid)
-                        .setCallingPackage(srec.packageName)
-                        .setCallingFeatureId(parent.launchedFromFeatureId)
-                        .setRealCallingPid(-1)
-                        .setRealCallingUid(callingUid)
-                        .setComponentSpecified(true)
-                        .execute();
-                foundParentInTask = isStartResultSuccessful(res);
-                if (res == ActivityManager.START_SUCCESS) {
-                    parent.finishIfPossible(resultCode, resultData, resultGrants,
-                            "navigate-top", true /* oomAdj */);
-                }
-            } catch (RemoteException e) {
-                foundParentInTask = false;
+            // TODO(b/64750076): Check if calling pid should really be -1.
+            final int res = mAtmService.getActivityStartController()
+                    .obtainStarter(destIntent, "navigateUpTo")
+                    .setResolvedType(resolvedType)
+                    .setUserId(srec.mUserId)
+                    .setCaller(srec.app.getThread())
+                    .setResultTo(parent.token)
+                    .setIntentGrants(destGrants)
+                    .setCallingPid(-1)
+                    .setCallingUid(callingUid)
+                    .setCallingPackage(srec.packageName)
+                    .setCallingFeatureId(parent.launchedFromFeatureId)
+                    .setRealCallingPid(-1)
+                    .setRealCallingUid(callingUid)
+                    .setComponentSpecified(true)
+                    .execute();
+            foundParentInTask = isStartResultSuccessful(res);
+            if (res == ActivityManager.START_SUCCESS) {
+                parent.finishIfPossible(resultCode, resultData, resultGrants,
+                        "navigate-top", true /* oomAdj */);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -5417,10 +5437,7 @@
 
         if (timeTracker != null) {
             // The caller wants a time tracker associated with this task.
-            final PooledConsumer c = PooledLambda.obtainConsumer(ActivityRecord::setAppTimeTracker,
-                    PooledLambda.__(ActivityRecord.class), timeTracker);
-            tr.forAllActivities(c);
-            c.recycle();
+            tr.forAllActivities(a -> { a.appTimeTracker = timeTracker; });
         }
 
         try {
@@ -5591,11 +5608,11 @@
         try {
             // TODO: Why not just set this on the root task directly vs. on each tasks?
             // Update override configurations of all tasks in the root task.
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    Task::processTaskResizeBounds, PooledLambda.__(Task.class),
-                    displayedBounds);
-            forAllTasks(c, true /* traverseTopToBottom */);
-            c.recycle();
+            forAllTasks(task -> {
+                if (task.isResizeable()) {
+                    task.setBounds(displayedBounds);
+                }
+            }, true /* traverseTopToBottom */);
 
             if (!deferResume) {
                 ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
@@ -5606,12 +5623,6 @@
         }
     }
 
-    private static void processTaskResizeBounds(Task task, Rect displayedBounds) {
-        if (!task.isResizeable()) return;
-
-        task.setBounds(displayedBounds);
-    }
-
     boolean willActivityBeVisible(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         if (r == null) {
@@ -5697,22 +5708,15 @@
 
         // All activities that came from the package must be
         // restarted as if there was a config change.
-        PooledConsumer c = PooledLambda.obtainConsumer(Task::restartPackage,
-                PooledLambda.__(ActivityRecord.class), starting, packageName);
-        forAllActivities(c);
-        c.recycle();
-
-        return starting;
-    }
-
-    private static void restartPackage(
-            ActivityRecord r, ActivityRecord starting, String packageName) {
-        if (r.info.packageName.equals(packageName)) {
+        forAllActivities(r -> {
+            if (!r.info.packageName.equals(packageName)) return;
             r.forceNewConfig = true;
             if (starting != null && r == starting && r.mVisibleRequested) {
                 r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
             }
-        }
+        });
+
+        return starting;
     }
 
     Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index de4c84c..4f1a561 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -298,6 +298,9 @@
 
     final Point mLastSurfaceSize = new Point();
 
+    /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */
+    boolean mVisibleRequested;
+
     private final Rect mTmpBounds = new Rect();
     private final Rect mTmpFullBounds = new Rect();
     /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
@@ -1716,7 +1719,7 @@
                 ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
                 prev = prev.completeFinishing(false /* updateVisibility */,
                         "completePausedLocked");
-            } else if (prev.hasProcess()) {
+            } else if (prev.attachedToProcess()) {
                 ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                 + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
                         prev.mVisibleRequested);
@@ -2382,6 +2385,14 @@
         }
     }
 
+    void sendTaskFragmentParentInfoChanged() {
+        final Task parentTask = getParent().asTask();
+        if (mTaskFragmentOrganizer != null && parentTask != null) {
+            mTaskFragmentOrganizerController
+                    .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask);
+        }
+    }
+
     private void sendTaskFragmentAppeared() {
         if (mTaskFragmentOrganizer != null) {
             mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
@@ -2417,7 +2428,7 @@
                 mRemoteToken.toWindowContainerToken(),
                 getConfiguration(),
                 getNonFinishingActivityCount(),
-                isVisible(),
+                isVisibleRequested(),
                 childActivities,
                 positionInParent,
                 mClearedTaskForReuse,
@@ -2669,6 +2680,18 @@
         return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
     }
 
+    void onActivityVisibleRequestedChanged() {
+        final boolean isVisibleRequested = isVisibleRequested();
+        if (mVisibleRequested == isVisibleRequested) {
+            return;
+        }
+        mVisibleRequested = isVisibleRequested;
+        final TaskFragment parentTf = getParent().asTaskFragment();
+        if (parentTf != null) {
+            parentTf.onActivityVisibleRequestedChanged();
+        }
+    }
+
     String toFullString() {
         final StringBuilder sb = new StringBuilder(128);
         sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 8c037a7..2d5c989 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,6 +49,7 @@
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
@@ -118,10 +119,10 @@
         private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>();
 
         /**
-         * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the
+         * Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the
          * organizer.
          */
-        private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs =
+        private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos =
                 new SparseArray<>();
 
         /**
@@ -225,7 +226,7 @@
                 taskId = mTaskFragmentTaskIds.remove(tf);
                 if (!mTaskFragmentTaskIds.containsValue(taskId)) {
                     // No more TaskFragment in the Task.
-                    mLastSentTaskFragmentParentConfigs.remove(taskId);
+                    mLastSentTaskFragmentParentInfos.remove(taskId);
                 }
             } else {
                 // This can happen if the appeared wasn't sent before remove.
@@ -260,25 +261,27 @@
         }
 
         @Nullable
-        TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(
-                @NonNull Task task) {
+        TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) {
             final int taskId = task.mTaskId;
             // Check if the parent info is different from the last reported parent info.
-            final Configuration taskConfig = task.getConfiguration();
-            final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId);
-            if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig)
-                    && taskConfig.windowConfiguration.getWindowingMode()
-                    == lastParentConfig.windowConfiguration.getWindowingMode()) {
+            final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo();
+            final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos
+                    .get(taskId);
+            final Configuration lastParentConfig = lastParentInfo != null
+                    ? lastParentInfo.getConfiguration() : null;
+            if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo)
+                    && configurationsAreEqualForOrganizer(parentInfo.getConfiguration(),
+                            lastParentConfig)) {
                 return null;
             }
 
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
                     "TaskFragment parent info changed name=%s parentTaskId=%d",
                     task.getName(), taskId);
-            mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig));
+            mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo));
             return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                     .setTaskId(taskId)
-                    .setTaskConfiguration(taskConfig);
+                    .setTaskFragmentParentInfo(parentInfo);
         }
 
         @NonNull
@@ -646,6 +649,34 @@
                 .build());
     }
 
+    void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+            @NonNull Task task) {
+        validateAndGetState(organizer);
+        final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent(
+                organizer, task);
+        if (pendingEvent == null) {
+            addPendingEvent(new PendingTaskFragmentEvent.Builder(
+                    PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
+                    .setTask(task)
+                    .build());
+        }
+    }
+
+    @Nullable
+    private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
+            @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
+        final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
+                .get(organizer.asBinder());
+        for (int i = events.size() - 1; i >= 0; i--) {
+            final PendingTaskFragmentEvent event = events.get(i);
+            if (task == event.mTask
+                    && event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
+                return event;
+            }
+        }
+        return null;
+    }
+
     private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) {
         mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event);
     }
@@ -848,7 +879,9 @@
     }
 
     private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
-        if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) {
+        if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
+                // Always send parent info changed to update task visibility
+                || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 723aa19..cb29e3f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -42,13 +42,13 @@
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
 
@@ -1296,7 +1296,7 @@
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "        sibling is a participant with mode %s",
                     TransitionInfo.modeToString(siblingMode));
-            if (mode != siblingMode) {
+            if (reduceMode(mode) != reduceMode(siblingMode)) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "          SKIP: common mode mismatch. was %s",
                         TransitionInfo.modeToString(mode));
@@ -1306,6 +1306,16 @@
         return true;
     }
 
+    /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
+    @TransitionInfo.TransitionMode
+    private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
+        switch (mode) {
+            case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
+            case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
+            default: return mode;
+        }
+    }
+
     /**
      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
      *
@@ -1807,7 +1817,7 @@
         @TransitionInfo.TransitionMode
         int getTransitMode(@NonNull WindowContainer wc) {
             if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
-                return TRANSIT_CLOSE;
+                return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
             }
             final boolean nowVisible = wc.isVisibleRequested();
             if (nowVisible == mVisible) {
@@ -1844,12 +1854,10 @@
             final ActivityRecord record = wc.asActivityRecord();
             if (record != null) {
                 parentTask = record.getTask();
-                if (record.mUseTransferredAnimation) {
-                    flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-                }
                 if (record.mVoiceInteraction) {
                     flags |= FLAG_IS_VOICE_INTERACTION;
                 }
+                flags |= record.mTransitionChangeFlags;
             }
             final TaskFragment taskFragment = wc.asTaskFragment();
             if (taskFragment != null && task == null) {
@@ -1860,14 +1868,12 @@
                     // Whether this is in a Task with embedded activity.
                     flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
                 }
-                final Rect taskBounds = parentTask.getBounds();
-                final Rect startBounds = mAbsoluteBounds;
-                final Rect endBounds = wc.getBounds();
-                if (taskBounds.width() == startBounds.width()
-                        && taskBounds.height() == startBounds.height()
-                        && taskBounds.width() == endBounds.width()
-                        && taskBounds.height() == endBounds.height()) {
-                    // Whether the container fills the Task bounds before and after the transition.
+                if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) {
+                    // The starting window should cover all windows inside the leaf Task.
+                    flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
+                }
+                if (isWindowFillingTask(wc, parentTask)) {
+                    // Whether the container fills its parent Task bounds.
                     flags |= FLAG_FILLS_TASK;
                 }
             }
@@ -1889,6 +1895,22 @@
             }
             return flags;
         }
+
+        /** Whether the container fills its parent Task bounds before and after the transition. */
+        private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
+            final Rect taskBounds = parentTask.getBounds();
+            final int taskWidth = taskBounds.width();
+            final int taskHeight = taskBounds.height();
+            final Rect startBounds = mAbsoluteBounds;
+            final Rect endBounds = wc.getBounds();
+            // Treat it as filling the task if it is not visible.
+            final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
+                    || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
+            final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
+                    || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
+            return isInvisibleOrFillingTaskBeforeTransition
+                    && isInVisibleOrFillingTaskAfterTransition;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0fd3e9b..81d6795 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -197,7 +197,7 @@
                 && animatingContainer.getAnimation() != null
                 && animatingContainer.getAnimation().getShowWallpaper();
         final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
-        if (isRecentsTransitionTarget(w)) {
+        if (isRecentsTransitionTarget(w) || isBackNavigationTarget(w)) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
             mFindResults.setWallpaperTarget(w);
             return true;
@@ -237,6 +237,12 @@
         return controller != null && controller.isWallpaperVisible(w);
     }
 
+    private boolean isBackNavigationTarget(WindowState w) {
+        // The window is in animating by back navigation and set to show wallpaper.
+        final BackNavigationController controller = mService.mAtmService.mBackNavigationController;
+        return controller != null && controller.isWallpaperVisible(w);
+    }
+
     /**
      * @see #computeLastWallpaperZoomOut()
      */
@@ -822,6 +828,12 @@
             if (mService.getRecentsAnimationController() != null) {
                 mService.getRecentsAnimationController().startAnimation();
             }
+
+            // If there was a pending back navigation animation that would show wallpaper, start
+            // the animation due to it was skipped in previous surface placement.
+            if (mService.mAtmService.mBackNavigationController != null) {
+                mService.mAtmService.mBackNavigationController.startAnimation();
+            }
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8cc0d5d..9763df6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -477,7 +477,10 @@
             }
             mLocalInsetsSourceProviders.remove(insetsTypes[i]);
         }
-        mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+        // Update insets if this window is attached.
+        if (mDisplayContent != null) {
+            mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+        }
     }
 
     /**
@@ -2195,6 +2198,17 @@
                 callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
     }
 
+    @Nullable
+    TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final TaskFragment tf = mChildren.get(i).getTaskFragment(callback);
+            if (tf != null) {
+                return tf;
+            }
+        }
+        return null;
+    }
+
     WindowState getWindow(Predicate<WindowState> callback) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState w = mChildren.get(i).getWindow(callback);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d11fc9..502b4bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
@@ -308,8 +309,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.view.WindowManagerPolicyThread;
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
@@ -318,6 +317,7 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
@@ -473,10 +473,7 @@
         public void onVrStateChanged(boolean enabled) {
             synchronized (mGlobalLock) {
                 mVrModeEnabled = enabled;
-                final PooledConsumer c = PooledLambda.obtainConsumer(
-                        DisplayPolicy::onVrStateChangedLw, PooledLambda.__(), enabled);
-                mRoot.forAllDisplayPolicies(c);
-                c.recycle();
+                mRoot.forAllDisplayPolicies(p -> p.onVrStateChangedLw(enabled));
             }
         }
     };
@@ -507,15 +504,9 @@
     };
 
     /**
-     * Current user when multi-user is enabled. Don't show windows of
-     * non-current user. Also see mCurrentProfileIds.
+     * Current user when multi-user is enabled. Don't show windows of non-current user.
      */
-    int mCurrentUserId;
-    /**
-     * Users that are profiles of the current user. These are also allowed to show windows
-     * on the current user.
-     */
-    int[] mCurrentProfileIds = new int[] {};
+    @UserIdInt int mCurrentUserId;
 
     final Context mContext;
 
@@ -537,6 +528,7 @@
 
     final IActivityManager mActivityManager;
     final ActivityManagerInternal mAmInternal;
+    final UserManagerInternal mUmInternal;
 
     final AppOpsManager mAppOps;
     final PackageManagerInternal mPmInternal;
@@ -903,11 +895,8 @@
             }
             mPointerLocationEnabled = enablePointerLocation;
             synchronized (mGlobalLock) {
-                final PooledConsumer c = PooledLambda.obtainConsumer(
-                        DisplayPolicy::setPointerLocationEnabled, PooledLambda.__(),
-                        mPointerLocationEnabled);
-                mRoot.forAllDisplayPolicies(c);
-                c.recycle();
+                mRoot.forAllDisplayPolicies(
+                        p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
             }
         }
 
@@ -1017,12 +1006,6 @@
 
     final LatencyTracker mLatencyTracker;
 
-    /**
-     * Whether the UI is currently running in touch mode (not showing
-     * navigational focus because the user is directly pressing the screen).
-     */
-    private boolean mInTouchMode;
-
     private ViewServer mViewServer;
     final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
     boolean mWindowsChanged = false;
@@ -1180,10 +1163,6 @@
                 com.android.internal.R.bool.config_sf_limitedAlpha);
         mHasPermanentDpad = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_hasPermanentDpad);
-        mInTouchMode = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_defaultInTouchMode);
-        inputManager.setInTouchMode(mInTouchMode, MY_PID, MY_UID, /* hasPermission= */ true,
-                DEFAULT_DISPLAY);
         mDrawLockTimeoutMillis = context.getResources().getInteger(
                 com.android.internal.R.integer.config_drawLockTimeoutMillis);
         mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
@@ -1266,6 +1245,7 @@
 
         mActivityManager = ActivityManager.getService();
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mUmInternal = LocalServices.getService(UserManagerInternal.class);
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
         AppOpsManager.OnOpChangedInternalListener opListener =
                 new AppOpsManager.OnOpChangedInternalListener() {
@@ -1800,8 +1780,7 @@
             if (displayPolicy.areSystemBarsForcedConsumedLw()) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
             }
-
-            if (mInTouchMode) {
+            if (displayContent.isInTouchMode()) {
                 res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
             }
             if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
@@ -1836,8 +1815,12 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                     + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
 
-            if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
-                    || displayPolicy.updateDecorInsetsInfoIfNeeded(win)) {
+            boolean needToSendNewConfiguration =
+                    win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
+            if (win.providesNonDecorInsets()) {
+                needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
+            }
+            if (needToSendNewConfiguration) {
                 displayContent.sendNewConfiguration();
             }
 
@@ -2306,8 +2289,8 @@
                         & WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
                     win.mLayoutNeeded = true;
                 }
-                if (layoutChanged) {
-                    configChanged = displayPolicy.updateDecorInsetsInfoIfNeeded(win);
+                if (layoutChanged && win.providesNonDecorInsets()) {
+                    configChanged = displayPolicy.updateDecorInsetsInfo();
                 }
                 if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                         || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
@@ -3137,10 +3120,7 @@
 
     @Override
     public void onPowerKeyDown(boolean isScreenOn) {
-        final PooledConsumer c = PooledLambda.obtainConsumer(
-                DisplayPolicy::onPowerKeyDown, PooledLambda.__(), isScreenOn);
-        mRoot.forAllDisplayPolicies(c);
-        c.recycle();
+        mRoot.forAllDisplayPolicies(p -> p.onPowerKeyDown(isScreenOn));
     }
 
     @Override
@@ -3573,16 +3553,9 @@
                 confirm);
     }
 
-    public void setCurrentProfileIds(final int[] currentProfileIds) {
-        synchronized (mGlobalLock) {
-            mCurrentProfileIds = currentProfileIds;
-        }
-    }
-
-    public void setCurrentUser(final int newUserId, final int[] currentProfileIds) {
+    public void setCurrentUser(@UserIdInt int newUserId) {
         synchronized (mGlobalLock) {
             mCurrentUserId = newUserId;
-            mCurrentProfileIds = currentProfileIds;
             mPolicy.setCurrentUserLw(newUserId);
             mKeyguardDisableHandler.setCurrentUser(newUserId);
 
@@ -3605,12 +3578,8 @@
     }
 
     /* Called by WindowState */
-    boolean isCurrentProfile(int userId) {
-        if (userId == mCurrentUserId) return true;
-        for (int i = 0; i < mCurrentProfileIds.length; i++) {
-            if (mCurrentProfileIds[i] == userId) return true;
-        }
-        return false;
+    boolean isUserVisible(@UserIdInt int userId) {
+        return mUmInternal.isUserVisible(userId);
     }
 
     public void enableScreenAfterBoot() {
@@ -3817,17 +3786,43 @@
     /**
      * Sets the touch mode state.
      *
+     * If {@code com.android.internal.R.bool.config_perDisplayFocusEnabled} is set to true, then
+     * only the display represented by the {@code displayId} parameter will be requested to switch
+     * the touch mode state. Otherwise all all displays will be requested to switch their touch mode
+     * state (disregarding {@code displayId} parameter).
+     *
      * To be able to change touch mode state, the caller must either own the focused window, or must
      * have the {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE} permission. Instrumented
      * process, sourced with {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE}, may switch
      * touch mode at any time.
      *
-     * @param mode the touch mode to set
+     * @param inTouch   the touch mode to set
+     * @param displayId the target display id
      */
     @Override // Binder call
-    public void setInTouchMode(boolean mode) {
+    public void setInTouchMode(boolean inTouch, int displayId) {
+        boolean perDisplayFocusEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+        setInTouchMode(inTouch, displayId, perDisplayFocusEnabled);
+    }
+
+    /**
+     * Sets the touch mode state on all displays (disregarding the value of
+     * {@code com.android.internal.R.bool.config_perDisplayFocusEnabled}).
+     *
+     * @param inTouch the touch mode to set
+     */
+    @Override // Binder call
+    public void setInTouchModeOnAllDisplays(boolean inTouch) {
+        setInTouchMode(inTouch, /* any display id */ DEFAULT_DISPLAY,
+                /* perDisplayFocusEnabled= */ false);
+    }
+
+    private void setInTouchMode(boolean inTouch, int displayId, boolean perDisplayFocusEnabled) {
         synchronized (mGlobalLock) {
-            if (mInTouchMode == mode) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (perDisplayFocusEnabled && (displayContent == null
+                    || displayContent.isInTouchMode() == inTouch)) {
                 return;
             }
             final int pid = Binder.getCallingPid();
@@ -3835,13 +3830,27 @@
             final boolean hasPermission =
                     mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
                             || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
-                                                      /* printlog= */ false);
+                            /* printlog= */ false);
             final long token = Binder.clearCallingIdentity();
             try {
-                // TODO(b/198499018): Add displayId parameter indicating the target display.
-                // For now, will just pass DEFAULT_DISPLAY for displayId.
-                if (mInputManager.setInTouchMode(mode, pid, uid, hasPermission, DEFAULT_DISPLAY)) {
-                    mInTouchMode = mode;
+                // If perDisplayFocusEnabled is set, then just update the display pointed by
+                // displayId
+                if (perDisplayFocusEnabled) {
+                    if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
+                        displayContent.setInTouchMode(inTouch);
+                    }
+                } else {  // Otherwise update all displays
+                    final int displayCount = mRoot.mChildren.size();
+                    for (int i = 0; i < displayCount; ++i) {
+                        DisplayContent dc = mRoot.mChildren.get(i);
+                        if (dc.isInTouchMode() == inTouch) {
+                            continue;
+                        }
+                        if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
+                                dc.mDisplayId)) {
+                            dc.setInTouchMode(inTouch);
+                        }
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -3849,9 +3858,18 @@
         }
     }
 
-    boolean getInTouchMode() {
+    /**
+     * Returns the touch mode state for the display id passed as argument.
+     */
+    @Override  // Binder call
+    public boolean isInTouchMode(int displayId) {
         synchronized (mGlobalLock) {
-            return mInTouchMode;
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                throw new IllegalStateException("No touch mode is defined for displayId {"
+                        + displayId + "}");
+            }
+            return displayContent.isInTouchMode();
         }
     }
 
@@ -5994,7 +6012,11 @@
         if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
                 && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
             ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
-            w.setOrientationChanging(true);
+            // WindowsState#reportResized won't tell invisible requested window to redraw,
+            // so do not set it as changing orientation to avoid affecting draw state.
+            if (w.isVisibleRequested()) {
+                w.setOrientationChanging(true);
+            }
             if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
                 mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
                 // XXX should probably keep timeout from
@@ -6658,7 +6680,6 @@
             pw.print("  Minimum task size of display#"); pw.print(displayId);
             pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
         });
-        pw.print("  mInTouchMode="); pw.println(mInTouchMode);
         pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
         pw.print("  mLastDisplayFreezeDuration=");
                 TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
@@ -8400,10 +8421,7 @@
     void onLockTaskStateChanged(int lockTaskState) {
         // TODO: pass in displayId to determine which display the lock task state changed
         synchronized (mGlobalLock) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    DisplayPolicy::onLockTaskStateChangedLw, PooledLambda.__(), lockTaskState);
-            mRoot.forAllDisplayPolicies(c);
-            c.recycle();
+            mRoot.forAllDisplayPolicies(p -> p.onLockTaskStateChangedLw(lockTaskState));
         }
     }
 
@@ -8512,7 +8530,10 @@
         if (mFocusedInputTarget != t && mFocusedInputTarget != null) {
             mFocusedInputTarget.handleTapOutsideFocusOutsideSelf();
         }
+        // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities.
+        mAtmService.mTaskSupervisor.mUserLeaving = true;
         t.handleTapOutsideFocusInsideSelf();
+        mAtmService.mTaskSupervisor.mUserLeaving = false;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9456f0f..2304ab4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -25,6 +25,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER;
@@ -45,6 +46,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -90,8 +92,6 @@
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
 
@@ -241,8 +241,18 @@
     }
 
     @Override
-    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+    public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+        return startTransition(type, null /* transitionToken */, t);
+    }
+
+    @Override
+    public void startTransition(@NonNull IBinder transitionToken,
             @Nullable WindowContainerTransaction t) {
+        startTransition(-1 /* unused type */, transitionToken, t);
+    }
+
+    private IBinder startTransition(@WindowManager.TransitionType int type,
+            @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
         enforceTaskPermission("startTransition()");
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
@@ -481,7 +491,7 @@
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
-            ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+            final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
             Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
                     t.getChanges().entrySet().iterator();
             while (entries.hasNext()) {
@@ -591,16 +601,10 @@
                 mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
                 mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
             } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
-                final PooledConsumer f = PooledLambda.obtainConsumer(
-                        ActivityRecord::ensureActivityConfiguration,
-                        PooledLambda.__(ActivityRecord.class), 0,
-                        true /* preserveWindow */);
-                try {
-                    for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
-                        haveConfigChanges.valueAt(i).forAllActivities(f);
-                    }
-                } finally {
-                    f.recycle();
+                for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+                    haveConfigChanges.valueAt(i).forAllActivities(r -> {
+                        r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
+                    });
                 }
             }
 
@@ -715,7 +719,7 @@
 
         final int childWindowingMode = c.getActivityWindowingMode();
         if (childWindowingMode > -1) {
-            tr.setActivityWindowingMode(childWindowingMode);
+            tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
         }
 
         if (t != null) {
@@ -1007,6 +1011,20 @@
                         isInLockTaskMode);
                 break;
             }
+            case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: {
+                final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
+                if (activity == null || activity.finishing) {
+                    break;
+                }
+                if (activity.isVisible()) {
+                    // Prevent the transition from being executed too early if the activity is
+                    // visible.
+                    activity.finishIfPossible("finish-activity-op", false /* oomAdj */);
+                } else {
+                    activity.destroyIfPossible("finish-activity-op");
+                }
+                break;
+            }
             case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
                 mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
                         "launchTask HierarchyOp");
@@ -1542,10 +1560,6 @@
         return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
     }
 
-    private void enforceTaskPermission(String func) {
-        mService.enforceTaskPermission(func);
-    }
-
     private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
         if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
                 .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
@@ -1620,6 +1634,9 @@
                                 organizer);
                     }
                     break;
+                case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
+                    // Allow finish activity if it has the activity token.
+                    break;
                 default:
                     // Other types of hierarchy changes are not allowed.
                     String msg = "Permission Denial: " + func + " from pid="
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index df7b8bb..0f5184a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2042,9 +2042,13 @@
      * it must be drawn before allDrawn can become true.
      */
     boolean isInteresting() {
+        final RecentsAnimationController recentsAnimationController =
+                mWmService.getRecentsAnimationController();
         return mActivityRecord != null && !mAppDied
                 && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
-                && mViewVisibility == View.VISIBLE;
+                && mViewVisibility == View.VISIBLE
+                && (recentsAnimationController == null
+                         || recentsAnimationController.isInterestingForAllDrawn(this));
     }
 
     /**
@@ -2622,11 +2626,19 @@
                 }
             }
 
+            // Check if window provides non decor insets before clearing its provided insets.
+            final boolean windowProvidesNonDecorInsets = providesNonDecorInsets();
+
             removeImmediately();
             // Removing a visible window may affect the display orientation so just update it if
             // needed. Also recompute configuration if it provides screen decor insets.
-            if ((wasVisible && displayContent.updateOrientation())
-                    || displayContent.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(this)) {
+            boolean needToSendNewConfiguration = wasVisible && displayContent.updateOrientation();
+            if (windowProvidesNonDecorInsets) {
+                needToSendNewConfiguration |=
+                        displayContent.getDisplayPolicy().updateDecorInsetsInfo();
+            }
+
+            if (needToSendNewConfiguration) {
                 displayContent.sendNewConfiguration();
             }
             mWmService.updateFocusedWindowLocked(isFocused()
@@ -3467,7 +3479,13 @@
                     "Setting visibility of " + this + ": " + clientVisible);
             mClient.dispatchAppVisibility(clientVisible);
         } catch (RemoteException e) {
+            // The remote client fails to process the visibility message. That means it is in a
+            // wrong state. E.g. the binder buffer is running out or the binder threads are dead.
+            // The window visibility is out-of-sync that may cause blank content or left over, so
+            // just kill it. And if it is a window of foreground activity, the activity can be
+            // restarted automatically if needed.
             Slog.w(TAG, "Exception thrown during dispatchAppVisibility " + this, e);
+            android.os.Process.killProcess(mSession.mPid);
         }
     }
 
@@ -3684,7 +3702,7 @@
         }
 
         return win.showForAllUsers()
-                || mWmService.isCurrentProfile(win.mShowUserId);
+                || mWmService.isUserVisible(win.mShowUserId);
     }
 
     private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
@@ -3850,7 +3868,7 @@
         // configuration update when the window has requested to be hidden. Doing so can lead to
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
-        if (useLatestConfig || (relayoutVisible && (shouldCheckTokenVisibleRequested()
+        if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
                 || mToken.isVisibleRequested()))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
@@ -4562,7 +4580,7 @@
     float translateToWindowX(float x) {
         float winX = x - mWindowFrames.mFrame.left;
         if (mGlobalScale != 1f) {
-            winX *= mGlobalScale;
+            winX *= mInvGlobalScale;
         }
         return winX;
     }
@@ -4570,7 +4588,7 @@
     float translateToWindowY(float y) {
         float winY = y - mWindowFrames.mFrame.top;
         if (mGlobalScale != 1f) {
-            winY *= mGlobalScale;
+            winY *= mInvGlobalScale;
         }
         return winY;
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 5d6ffd8..7e93d62 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,6 +53,7 @@
         "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
         "com_android_server_stats_pull_StatsPullAtomService.cpp",
         "com_android_server_storage_AppFuseBridge.cpp",
+        "com_android_server_SystemClockTime.cpp",
         "com_android_server_SystemServer.cpp",
         "com_android_server_tv_TvUinputBridge.cpp",
         "com_android_server_tv_TvInputHal.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 9abf107..2584b86 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -12,6 +12,7 @@
 per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
 
 per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
+per-file com_android_server_SystemClock* = file:/services/core/java/com/android/server/timedetector/OWNERS
 per-file com_android_server_Usb* = file:/services/usb/OWNERS
 per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
diff --git a/services/core/jni/com_android_server_SystemClockTime.cpp b/services/core/jni/com_android_server_SystemClockTime.cpp
new file mode 100644
index 0000000..9db4c42
--- /dev/null
+++ b/services/core/jni/com_android_server_SystemClockTime.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "SystemClockTime"
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <linux/rtc.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "jni.h"
+
+namespace android {
+
+class SystemClockImpl {
+public:
+    SystemClockImpl(const std::string &rtc_dev) : rtc_dev{rtc_dev} {}
+
+    int setTime(struct timeval *tv);
+
+private:
+    std::string rtc_dev;
+};
+
+int SystemClockImpl::setTime(struct timeval *tv) {
+    if (settimeofday(tv, NULL) == -1) {
+        ALOGV("settimeofday() failed: %s", strerror(errno));
+        return -1;
+    }
+
+    android::base::unique_fd fd{open(rtc_dev.c_str(), O_RDWR)};
+    if (!fd.ok()) {
+        ALOGE("Unable to open %s: %s", rtc_dev.c_str(), strerror(errno));
+        return -1;
+    }
+
+    struct tm tm;
+    if (!gmtime_r(&tv->tv_sec, &tm)) {
+        ALOGV("gmtime_r() failed: %s", strerror(errno));
+        return -1;
+    }
+
+    struct rtc_time rtc = {};
+    rtc.tm_sec = tm.tm_sec;
+    rtc.tm_min = tm.tm_min;
+    rtc.tm_hour = tm.tm_hour;
+    rtc.tm_mday = tm.tm_mday;
+    rtc.tm_mon = tm.tm_mon;
+    rtc.tm_year = tm.tm_year;
+    rtc.tm_wday = tm.tm_wday;
+    rtc.tm_yday = tm.tm_yday;
+    rtc.tm_isdst = tm.tm_isdst;
+    if (ioctl(fd, RTC_SET_TIME, &rtc) == -1) {
+        ALOGV("RTC_SET_TIME ioctl failed: %s", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+static jlong com_android_server_SystemClockTime_init(JNIEnv *, jobject) {
+    // Find the wall clock RTC. We expect this always to be /dev/rtc0, but
+    // check the /dev/rtc symlink first so that legacy devices that don't use
+    // rtc0 can add a symlink rather than need to carry a local patch to this
+    // code.
+    //
+    // TODO: if you're reading this in a world where all devices are using the
+    // GKI, you can remove the readlink and just assume /dev/rtc0.
+    std::string dev_rtc;
+    if (!android::base::Readlink("/dev/rtc", &dev_rtc)) {
+        dev_rtc = "/dev/rtc0";
+    }
+
+    std::unique_ptr<SystemClockImpl> system_clock{new SystemClockImpl(dev_rtc)};
+    return reinterpret_cast<jlong>(system_clock.release());
+}
+
+static jint com_android_server_SystemClockTime_setTime(JNIEnv *, jobject, jlong nativeData,
+                                                       jlong millis) {
+    SystemClockImpl *impl = reinterpret_cast<SystemClockImpl *>(nativeData);
+
+    if (millis <= 0 || millis / 1000LL >= std::numeric_limits<time_t>::max()) {
+        return -1;
+    }
+
+    struct timeval tv;
+    tv.tv_sec = (millis / 1000LL);
+    tv.tv_usec = ((millis % 1000LL) * 1000LL);
+
+    ALOGD("Setting time of day to sec=%ld", tv.tv_sec);
+
+    int ret = impl->setTime(&tv);
+    if (ret < 0) {
+        ALOGW("Unable to set rtc to %ld: %s", tv.tv_sec, strerror(errno));
+        ret = -1;
+    }
+    return ret;
+}
+
+static const JNINativeMethod sMethods[] = {
+        /* name, signature, funcPtr */
+        {"init", "()J", (void *)com_android_server_SystemClockTime_init},
+        {"setTime", "(JJ)I", (void *)com_android_server_SystemClockTime_setTime},
+};
+
+int register_com_android_server_SystemClockTime(JNIEnv *env) {
+    return jniRegisterNativeMethods(env, "com/android/server/SystemClockTime", sMethods,
+                                    NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 1d56078..d3d69ae 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -491,10 +491,10 @@
     compactProcessOrFallback(pid, compactionFlags);
 }
 
-static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
-        JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
-
-    jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
+                                                                  jint pid, jboolean freeze,
+                                                                  jint timeout_ms) {
+    jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
     if (retVal != 0 && retVal != -EAGAIN) {
         jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
     }
@@ -548,7 +548,7 @@
          (void*)com_android_server_am_CachedAppOptimizer_getMemoryFreedCompaction},
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
         {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
-        {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+        {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
         {"getBinderFreezeInfo", "(I)I",
          (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
         {"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 61f2b14..02e5061 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -17,6 +17,7 @@
 #include <android_util_Binder.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
@@ -33,6 +34,27 @@
     SurfaceComposerClient::destroyDisplay(token);
 }
 
+static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
+                                   jintArray jHdrTypes) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == nullptr || jHdrTypes == nullptr) return;
+
+    ScopedIntArrayRO hdrTypes(env, jHdrTypes);
+    size_t numHdrTypes = hdrTypes.size();
+
+    std::vector<ui::Hdr> hdrTypesVector;
+    hdrTypesVector.reserve(numHdrTypes);
+    for (int i = 0; i < numHdrTypes; i++) {
+        hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
+    }
+
+    status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
+    if (error != NO_ERROR) {
+        jniThrowExceptionFmt(env, "java/lang/SecurityException",
+                             "ACCESS_SURFACE_FLINGER is missing");
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sDisplayMethods[] = {
@@ -41,6 +63,8 @@
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
+    {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
+                (void*)nativeOverrideHdrTypes },
         // clang-format on
 };
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 78b4ce2..3f380e7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -168,8 +168,9 @@
     jmethodID constructor;
     jfieldID lightTypeInput;
     jfieldID lightTypePlayerId;
+    jfieldID lightTypeKeyboardBacklight;
     jfieldID lightCapabilityBrightness;
-    jfieldID lightCapabilityRgb;
+    jfieldID lightCapabilityColorRgb;
 } gLightClassInfo;
 
 static struct {
@@ -2011,25 +2012,28 @@
 
         jint jTypeId =
                 env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
-        jint jCapability = 0;
-
-        if (lightInfo.type == InputDeviceLightType::MONO) {
-            jCapability = env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityBrightness);
-        } else if (lightInfo.type == InputDeviceLightType::RGB ||
-                   lightInfo.type == InputDeviceLightType::MULTI_COLOR) {
-            jCapability =
-                env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityBrightness) |
-                env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityRgb);
+        if (lightInfo.type == InputDeviceLightType::INPUT) {
+            jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
         } else if (lightInfo.type == InputDeviceLightType::PLAYER_ID) {
             jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
                                                  gLightClassInfo.lightTypePlayerId);
+        } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+            jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+                                             gLightClassInfo.lightTypeKeyboardBacklight);
         } else {
             ALOGW("Unknown light type %d", lightInfo.type);
             continue;
         }
+
+        jint jCapability = 0;
+        if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)) {
+            jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+                                                  gLightClassInfo.lightCapabilityBrightness);
+        }
+        if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::RGB)) {
+            jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+                                                  gLightClassInfo.lightCapabilityColorRgb);
+        }
         ScopedLocalRef<jobject> lightObj(env,
                                          env->NewObject(gLightClassInfo.clazz,
                                                         gLightClassInfo.constructor,
@@ -2596,10 +2600,12 @@
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT", "I");
     gLightClassInfo.lightTypePlayerId =
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_PLAYER_ID", "I");
+    gLightClassInfo.lightTypeKeyboardBacklight =
+            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I");
     gLightClassInfo.lightCapabilityBrightness =
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I");
-    gLightClassInfo.lightCapabilityRgb =
-            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_RGB", "I");
+    gLightClassInfo.lightCapabilityColorRgb =
+            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_COLOR_RGB", "I");
 
     // ArrayList
     FIND_CLASS(gArrayListClassInfo.clazz, "java/util/ArrayList");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00bef09..00f851f 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
 int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
+int register_com_android_server_SystemClockTime(JNIEnv* env);
 };
 
 using namespace android;
@@ -122,5 +123,6 @@
     register_android_server_app_GameManagerService(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
+    register_com_android_server_SystemClockTime(env);
     return JNI_VERSION_1_4;
 }
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 267cff6..98e5f1d 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -351,6 +351,35 @@
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
+        <xs:sequence>
+        <!--  Thresholds as tenths of percent of current brightness level, at each level of
+            brightness -->
+            <xs:element name="brightnessThresholdPoints" type="thresholdPoints" maxOccurs="1" minOccurs="0">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="thresholdPoints">
+        <xs:sequence>
+            <xs:element type="thresholdPoint" name="brightnessThresholdPoint" maxOccurs="unbounded" minOccurs="1">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="thresholdPoint">
+        <xs:sequence>
+            <xs:element type="nonNegativeDecimal" name="threshold">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="nonNegativeDecimal" name="percentage">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
     </xs:complexType>
 
     <xs:complexType name="autoBrightness">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f8bff75..748ef4b 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,7 +13,9 @@
 
   public class BrightnessThresholds {
     ctor public BrightnessThresholds();
+    method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
     method @NonNull public final java.math.BigDecimal getMinimum();
+    method public final void setBrightnessThresholdPoints(com.android.server.display.config.ThresholdPoints);
     method public final void setMinimum(@NonNull java.math.BigDecimal);
   }
 
@@ -204,6 +206,19 @@
     method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
   }
 
+  public class ThresholdPoint {
+    ctor public ThresholdPoint();
+    method @NonNull public final java.math.BigDecimal getPercentage();
+    method @NonNull public final java.math.BigDecimal getThreshold();
+    method public final void setPercentage(@NonNull java.math.BigDecimal);
+    method public final void setThreshold(@NonNull java.math.BigDecimal);
+  }
+
+  public class ThresholdPoints {
+    ctor public ThresholdPoints();
+    method @NonNull public final java.util.List<com.android.server.display.config.ThresholdPoint> getBrightnessThresholdPoint();
+  }
+
   public class Thresholds {
     ctor public Thresholds();
     method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 76ed2b3..91f5c69 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -16,10 +16,18 @@
 
 package com.android.server.credentials;
 
+import static android.content.Context.CREDENTIAL_SERVICE;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialRequest;
+import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
+import android.credentials.IGetCredentialCallback;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -59,11 +67,17 @@
     @Override
     public void onStart() {
         Log.i(TAG, "onStart");
+        publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
     }
 
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
         @Override
-        public void getCredential() {
+        public ICancellationSignal executeGetCredential(
+                GetCredentialRequest request,
+                IGetCredentialCallback callback) {
+            // TODO: implement.
+            Log.i(TAG, "executeGetCredential");
+
             final int userId = UserHandle.getCallingUserId();
             synchronized (mLock) {
                 final CredentialManagerServiceImpl service = peekServiceForUserLocked(userId);
@@ -72,6 +86,19 @@
                     service.getCredential();
                 }
             }
+
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
+
+        @Override
+        public ICancellationSignal executeCreateCredential(
+                CreateCredentialRequest request,
+                ICreateCredentialCallback callback) {
+            // TODO: implement.
+            Log.i(TAG, "executeCreateCredential");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
         }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bb6bd4a..a561307 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -150,6 +150,7 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
@@ -363,6 +364,7 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.PasswordValidationError;
 import com.android.net.module.util.ProxyUtils;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.PersistentDataBlockManagerInternal;
@@ -1466,6 +1468,10 @@
             return mContext.getSystemService(AlarmManager.class);
         }
 
+        AlarmManagerInternal getAlarmManagerInternal() {
+            return LocalServices.getService(AlarmManagerInternal.class);
+        }
+
         ConnectivityManager getConnectivityManager() {
             return mContext.getSystemService(ConnectivityManager.class);
         }
@@ -2485,83 +2491,41 @@
                 reqPolicy, /* permission= */ null);
     }
 
-    @NonNull ActiveAdmin getDeviceOwnerLocked(final CallerIdentity caller) {
+    ActiveAdmin getDeviceOwnerLocked(@UserIdInt int userId) {
         ensureLocked();
         ComponentName doComponent = mOwners.getDeviceOwnerComponent();
-        Preconditions.checkState(doComponent != null,
-                "No device owner for user %d", caller.getUid());
-
-        // Use the user ID of the caller instead of mOwners.getDeviceOwnerUserId() because
-        // secondary, affiliated users will have their own admin.
-        ActiveAdmin doAdmin = getUserData(caller.getUserId()).mAdminMap.get(doComponent);
-        Preconditions.checkState(doAdmin != null,
-                "Device owner %s for user %d not found", doComponent,
-                        caller.getUid());
-
-        Preconditions.checkCallAuthorization(doAdmin.getUid() == caller.getUid(),
-                    "Admin %s is not owned by uid %d, but uid %d", doComponent,
-                            caller.getUid(), doAdmin.getUid());
-
-        Preconditions.checkCallAuthorization(
-                !caller.hasAdminComponent()
-                || doAdmin.info.getComponent().equals(caller.getComponentName()),
-                "Caller component %s is not device owner",
-                        caller.getComponentName());
-
+        ActiveAdmin doAdmin = getUserData(userId).mAdminMap.get(doComponent);
         return doAdmin;
     }
 
-    @NonNull ActiveAdmin getProfileOwnerLocked(final CallerIdentity caller) {
+    ActiveAdmin getProfileOwnerLocked(@UserIdInt int userId) {
         ensureLocked();
-        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(caller.getUserId());
-
-        Preconditions.checkState(poAdminComponent != null,
-                    "No profile owner for user %d", caller.getUid());
-
-        ActiveAdmin poAdmin = getUserData(caller.getUserId()).mAdminMap.get(poAdminComponent);
-        Preconditions.checkState(poAdmin != null,
-                    "No device profile owner for caller %d", caller.getUid());
-
-        Preconditions.checkCallAuthorization(poAdmin.getUid() == caller.getUid(),
-                    "Admin %s is not owned by uid %d", poAdminComponent,
-                            caller.getUid());
-
-        Preconditions.checkCallAuthorization(
-                !caller.hasAdminComponent()
-                || poAdmin.info.getComponent().equals(caller.getComponentName()),
-                "Caller component %s is not profile owner",
-                        caller.getComponentName());
-
+        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId);
+        ActiveAdmin poAdmin = getUserData(userId).mAdminMap.get(poAdminComponent);
         return poAdmin;
     }
 
     @NonNull ActiveAdmin getOrganizationOwnedProfileOwnerLocked(final CallerIdentity caller) {
-        final ActiveAdmin profileOwner = getProfileOwnerLocked(caller);
-
         Preconditions.checkCallAuthorization(
                 mOwners.isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()),
-                "Admin %s is not of an org-owned device",
-                        profileOwner.info.getComponent());
+                "Caller %s is not an admin of an org-owned device",
+                caller.getComponentName());
+        final ActiveAdmin profileOwner = getProfileOwnerLocked(caller.getUserId());
 
         return profileOwner;
     }
 
-    @NonNull ActiveAdmin getProfileOwnerOrDeviceOwnerLocked(final CallerIdentity caller) {
+    ActiveAdmin getProfileOwnerOrDeviceOwnerLocked(@UserIdInt int userId) {
         ensureLocked();
         // Try to find an admin which can use reqPolicy
-        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(caller.getUserId());
+        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId);
         final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent();
 
-        if (poAdminComponent == null && doAdminComponent == null) {
-            throw new IllegalStateException(
-                    String.format("No profile or device owner for user %d", caller.getUid()));
-        }
-
         if (poAdminComponent != null) {
-            return getProfileOwnerLocked(caller);
+            return getProfileOwnerLocked(userId);
         }
 
-        return getDeviceOwnerLocked(caller);
+        return getDeviceOwnerLocked(userId);
     }
 
     @NonNull ActiveAdmin getParentOfAdminIfRequired(ActiveAdmin admin, boolean parent) {
@@ -3960,8 +3924,6 @@
                             admins.add(admin);
                         }
                     }
-                } else {
-                    Slogf.w(LOG_TAG, "Unknown user type: " + userInfo);
                 }
             }
         });
@@ -4132,7 +4094,7 @@
         List<String> changedProviders = null;
 
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller);
+            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller.getUserId());
             if (activeAdmin.crossProfileWidgetProviders == null) {
                 activeAdmin.crossProfileWidgetProviders = new ArrayList<>();
             }
@@ -4167,7 +4129,7 @@
         List<String> changedProviders = null;
 
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller);
+            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller.getUserId());
             if (activeAdmin.crossProfileWidgetProviders == null
                     || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
                 return false;
@@ -4201,7 +4163,7 @@
         Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller);
+            ActiveAdmin activeAdmin = getProfileOwnerLocked(caller.getUserId());
             if (activeAdmin.crossProfileWidgetProviders == null
                     || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
                 return null;
@@ -4721,7 +4683,7 @@
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getParentOfAdminIfRequired(
-                    getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParent);
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
             if (admin.mPasswordComplexity != passwordComplexity) {
                 // We require the caller to explicitly clear any password quality requirements set
                 // on the parent DPM instance, to avoid the case where password requirements are
@@ -4990,7 +4952,7 @@
         // If caller has PO (or DO) throw or fail silently depending on its target SDK level.
         if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
             synchronized (getLockObject()) {
-                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
                 if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
                     Slogf.e(LOG_TAG, "DPC can no longer call resetPassword()");
                     return false;
@@ -5249,8 +5211,8 @@
         final int userHandle = caller.getUserId();
         boolean changed = false;
         synchronized (getLockObject()) {
-            ActiveAdmin ap = getParentOfAdminIfRequired(getProfileOwnerOrDeviceOwnerLocked(caller),
-                    parent);
+            ActiveAdmin ap = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             if (ap.strongAuthUnlockTimeout != timeoutMs) {
                 ap.strongAuthUnlockTimeout = timeoutMs;
                 saveSettingsLocked(userHandle);
@@ -5725,7 +5687,7 @@
         try {
             return setKeyChainGrantInternal(alias, hasGrant, granteeUid, caller.getUserHandle());
         } catch (IllegalArgumentException e) {
-            if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, packageName,
+            if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, callerPackage,
                     caller.getUserId())) {
                 throw e;
             }
@@ -6519,7 +6481,8 @@
         if (vpnPackage == null) {
             final String prevVpnPackage;
             synchronized (getLockObject()) {
-                prevVpnPackage = getProfileOwnerOrDeviceOwnerLocked(caller).mAlwaysOnVpnPackage;
+                prevVpnPackage = getProfileOwnerOrDeviceOwnerLocked(
+                        caller.getUserId()).mAlwaysOnVpnPackage;
                 // If the admin is clearing VPN package but hasn't configure any VPN previously,
                 // ignore it so that it doesn't interfere with user-configured VPNs.
                 if (TextUtils.isEmpty(prevVpnPackage)) {
@@ -6560,7 +6523,7 @@
                 .setInt(lockdownAllowlist != null ? lockdownAllowlist.size() : 0)
                 .write();
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!TextUtils.equals(vpnPackage, admin.mAlwaysOnVpnPackage)
                     || lockdown != admin.mAlwaysOnVpnLockdown) {
                 admin.mAlwaysOnVpnPackage = vpnPackage;
@@ -6947,7 +6910,7 @@
 
         final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mFactoryResetProtectionPolicy = policy;
             saveSettingsLocked(caller.getUserId());
         }
@@ -6992,7 +6955,7 @@
                 Preconditions.checkCallAuthorization(
                         isDefaultDeviceOwner(caller)
                                 || isProfileOwnerOfOrganizationOwnedDevice(caller));
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             }
         }
 
@@ -7635,11 +7598,14 @@
         final CallerIdentity caller = getCallerIdentity(who);
         if (parent) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+        } else {
+            Preconditions.checkCallAuthorization(isProfileOwner(caller)
+                    || isDeviceOwner(caller));
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin ap = getParentOfAdminIfRequired(getProfileOwnerOrDeviceOwnerLocked(caller),
-                    parent);
+            ActiveAdmin ap = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             if (ap.disableScreenCapture != disabled) {
                 ap.disableScreenCapture = disabled;
                 saveSettingsLocked(caller.getUserId());
@@ -7725,7 +7691,7 @@
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mNearbyNotificationStreamingPolicy != policy) {
                 admin.mNearbyNotificationStreamingPolicy = policy;
                 saveSettingsLocked(caller.getUserId());
@@ -7743,6 +7709,7 @@
         Preconditions.checkCallAuthorization(
                 isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
             if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
@@ -7765,7 +7732,7 @@
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mNearbyAppStreamingPolicy != policy) {
                 admin.mNearbyAppStreamingPolicy = policy;
                 saveSettingsLocked(caller.getUserId());
@@ -7783,6 +7750,7 @@
         Preconditions.checkCallAuthorization(
                 isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
             if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
@@ -7804,12 +7772,14 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwner(caller));
 
         boolean requireAutoTimeChanged = false;
         synchronized (getLockObject()) {
             Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
                     "Managed profile cannot set auto time required");
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.requireAutoTime != required) {
                 admin.requireAutoTime = required;
                 saveSettingsLocked(caller.getUserId());
@@ -8462,6 +8432,17 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the provided caller identity is of a device owner.
+     * @param caller identity of caller.
+     * @return true if {@code identity} is a device owner, false otherwise.
+     */
+    public boolean isDeviceOwner(CallerIdentity caller) {
+        synchronized (getLockObject()) {
+            return isDeviceOwnerLocked(caller);
+        }
+    }
+
     private boolean isDeviceOwnerLocked(CallerIdentity caller) {
         if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
             return false;
@@ -8886,11 +8867,11 @@
         final CallerIdentity caller = getCallerIdentity(who);
         final int userId = caller.getUserId();
         Preconditions.checkCallingUser(!isManagedProfile(userId));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         enforceUserUnlocked(userId);
         synchronized (getLockObject()) {
-            // Check if this is the profile owner who is calling
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
 
             mInjector.binderWithCleanCallingIdentity(() -> {
                 clearProfileOwnerLocked(admin, userId);
@@ -10265,6 +10246,8 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwner(caller));
 
         if (packageList != null) {
             int userId = caller.getUserId();
@@ -10296,7 +10279,7 @@
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.permittedAccessiblityServices = packageList;
             saveSettingsLocked(UserHandle.getCallingUserId());
         }
@@ -10321,7 +10304,7 @@
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return admin.permittedAccessiblityServices;
         }
     }
@@ -10454,7 +10437,7 @@
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getParentOfAdminIfRequired(
-                    getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParentInstance);
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParentInstance);
             admin.permittedInputMethods = packageList;
             saveSettingsLocked(caller.getUserId());
         }
@@ -10496,7 +10479,7 @@
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getParentOfAdminIfRequired(
-                    getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParentInstance);
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParentInstance);
             return admin.permittedInputMethods;
         }
     }
@@ -10586,9 +10569,9 @@
         if (!isManagedProfile(caller.getUserId())) {
             return false;
         }
-
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             admin.permittedNotificationListeners = packageList;
             saveSettingsLocked(caller.getUserId());
         }
@@ -10602,11 +10585,13 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             // API contract is to return null if there are no permitted cross-profile notification
             // listeners, including in Device Owner mode.
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return admin.permittedNotificationListeners;
         }
     }
@@ -11379,10 +11364,16 @@
             return;
         }
 
+        if (parent) {
+            Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
+        } else {
+            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        }
+
         int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
-                    getProfileOwnerOrDeviceOwnerLocked(caller), parent);
+                    getProfileOwnerOrDeviceOwnerLocked(userHandle), parent);
 
             if (isDefaultDeviceOwner(caller)) {
                 if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
@@ -11512,7 +11503,7 @@
 
         synchronized (getLockObject()) {
             final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
-                    getProfileOwnerOrDeviceOwnerLocked(caller), parent);
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             return activeAdmin.userRestrictions;
         }
     }
@@ -11773,8 +11764,10 @@
                 ap = getParentOfAdminIfRequired(getOrganizationOwnedProfileOwnerLocked(caller),
                         parent);
             } else {
-                Preconditions.checkCallAuthorization(!isFinancedDeviceOwner(caller));
-                ap = getParentOfAdminIfRequired(getProfileOwnerOrDeviceOwnerLocked(caller), parent);
+                Preconditions.checkCallAuthorization(
+                        isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+                ap = getParentOfAdminIfRequired(
+                        getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             }
 
             if (disabled) {
@@ -11895,7 +11888,7 @@
         Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             if (admin.disableCallerId != disabled) {
                 admin.disableCallerId = disabled;
                 saveSettingsLocked(caller.getUserId());
@@ -11918,7 +11911,7 @@
         Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.disableCallerId;
         }
     }
@@ -11946,7 +11939,7 @@
         Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             if (admin.disableContactsSearch != disabled) {
                 admin.disableContactsSearch = disabled;
                 saveSettingsLocked(caller.getUserId());
@@ -11969,7 +11962,7 @@
         Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.disableContactsSearch;
         }
     }
@@ -12051,7 +12044,7 @@
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.disableBluetoothContactSharing != disabled) {
                 admin.disableBluetoothContactSharing = disabled;
                 saveSettingsLocked(caller.getUserId());
@@ -12076,7 +12069,7 @@
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return admin.disableBluetoothContactSharing;
         }
     }
@@ -12560,8 +12553,10 @@
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
             return false;
         }
+        String logInfo = "DevicePolicyManager.setTimeZone()";
         mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getAlarmManager().setTimeZone(timeZone));
+                mInjector.getAlarmManagerInternal()
+                        .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH, logInfo));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_TIME_ZONE)
@@ -14406,9 +14401,10 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             admin.organizationColor = color;
             saveSettingsLocked(caller.getUserId());
         }
@@ -14447,9 +14443,10 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.organizationColor;
         }
     }
@@ -14481,9 +14478,10 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!TextUtils.equals(admin.organizationName, text)) {
                 admin.organizationName = (text == null || text.length() == 0)
                         ? null : text.toString();
@@ -14501,9 +14499,10 @@
 
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return admin.organizationName;
         }
     }
@@ -14563,7 +14562,7 @@
             return packageNames;
         }
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return mInjector.binderWithCleanCallingIdentity(() -> {
                 final List<String> excludedPkgs = removeInvalidPkgsForMeteredDataRestriction(
                         caller.getUserId(), packageNames);
@@ -14612,7 +14611,7 @@
                 "Admin %s does not own the profile", caller.getComponentName());
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             return admin.meteredDisabledPackages == null
                     ? new ArrayList<>() : admin.meteredDisabledPackages;
         }
@@ -16648,9 +16647,10 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             admin.mCrossProfileCalendarPackages = packageNames;
             saveSettingsLocked(caller.getUserId());
         }
@@ -16669,9 +16669,10 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.mCrossProfileCalendarPackages;
         }
     }
@@ -16744,10 +16745,11 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packageNames, "Package names is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         final List<String> previousCrossProfilePackages;
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             previousCrossProfilePackages = admin.mCrossProfilePackages;
             if (packageNames.equals(previousCrossProfilePackages)) {
                 return;
@@ -16777,9 +16779,10 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.mCrossProfilePackages;
         }
     }
@@ -17034,7 +17037,7 @@
                 "Common Criteria mode can only be controlled by a device owner or "
                         + "a profile owner on an organization-owned device.");
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mCommonCriteriaMode = enabled;
             saveSettingsLocked(caller.getUserId());
         }
@@ -17055,7 +17058,7 @@
                             + "a profile owner on an organization-owned device.");
 
             synchronized (getLockObject()) {
-                final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+                final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
                 return admin.mCommonCriteriaMode;
             }
         }
@@ -17078,7 +17081,7 @@
         Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             final long deadline = admin.mProfileOffDeadline;
             final int result = makeSuspensionReasons(admin.mSuspendPersonalApps,
                     deadline != 0 && mInjector.systemCurrentTimeMillis() > deadline);
@@ -17110,7 +17113,7 @@
 
         final int callingUserId = caller.getUserId();
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(callingUserId);
             boolean shouldSaveSettings = false;
             if (admin.mSuspendPersonalApps != suspended) {
                 admin.mSuspendPersonalApps = suspended;
@@ -17402,7 +17405,7 @@
 
         final int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(userId);
 
             // Ensure the timeout is long enough to avoid having bad user experience.
             if (timeoutMillis > 0 && timeoutMillis < MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD
@@ -17446,7 +17449,7 @@
         Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.mProfileMaximumTimeOffMillis;
         }
     }
@@ -17458,7 +17461,7 @@
         enforceUserUnlocked(caller.getUserId());
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             if (admin.mProfileOffDeadline > 0) {
                 admin.mProfileOffDeadline = 0;
                 saveSettingsLocked(caller.getUserId());
@@ -17473,7 +17476,7 @@
         enforceUserUnlocked(caller.getUserId());
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
             return admin.mProfileOffDeadline != 0;
         }
     }
@@ -18412,7 +18415,7 @@
                 "USB data signaling cannot be disabled.");
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mUsbDataSignalingEnabled != enabled) {
                 admin.mUsbDataSignalingEnabled = enabled;
                 saveSettingsLocked(caller.getUserId());
@@ -18447,7 +18450,8 @@
             // 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).mUsbDataSignalingEnabled;
+                return getProfileOwnerOrDeviceOwnerLocked(
+                        caller.getUserId()).mUsbDataSignalingEnabled;
             } else {
                 return isUsbDataSignalingEnabledInternalLocked();
             }
@@ -18503,7 +18507,7 @@
 
         boolean valueChanged = false;
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mWifiMinimumSecurityLevel != level) {
                 admin.mWifiMinimumSecurityLevel = level;
                 saveSettingsLocked(caller.getUserId());
@@ -18549,7 +18553,7 @@
 
         boolean changed = false;
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!Objects.equals(policy, admin.mWifiSsidPolicy)) {
                 admin.mWifiSsidPolicy = policy;
                 changed = true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 4343225..16876ac 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -45,13 +45,10 @@
             "mark-profile-owner-on-organization-owned-device";
 
     private static final String USER_OPTION = "--user";
-    private static final String NAME_OPTION = "--name";
     private static final String DO_ONLY_OPTION = "--device-owner-only";
 
     private final DevicePolicyManagerService mService;
     private int mUserId = UserHandle.USER_SYSTEM;
-    //TODO(b/240562946): remove mName once it is not used by setDeviceOwner
-    private String mName = "";
     private ComponentName mComponent;
     private boolean mSetDoOnly;
 
@@ -133,16 +130,16 @@
         pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
                 CMD_SET_ACTIVE_ADMIN, USER_OPTION);
         pw.printf("    Sets the given component as active admin for an existing user.\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] [ %s ]"
-                + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION, DO_ONLY_OPTION);
+        pw.printf("  %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+                + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
         pw.printf("    Sets the given component as active admin, and its package as device owner."
                 + "\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n",
-                CMD_SET_PROFILE_OWNER, USER_OPTION, NAME_OPTION);
+        pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+                CMD_SET_PROFILE_OWNER, USER_OPTION);
         pw.printf("    Sets the given component as active admin and profile owner for an existing "
                 + "user.\n\n");
-        pw.printf("  %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n",
-                CMD_REMOVE_ACTIVE_ADMIN, USER_OPTION, NAME_OPTION);
+        pw.printf("  %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+                CMD_REMOVE_ACTIVE_ADMIN, USER_OPTION);
         pw.printf("    Disables an active admin, the admin must have declared android:testOnly in "
                 + "the application in its manifest. This will also remove device and profile "
                 + "owners.\n\n");
@@ -245,7 +242,7 @@
     }
 
     private int runSetActiveAdmin(PrintWriter pw) {
-        parseArgs(/* canHaveName= */ false);
+        parseArgs();
         mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
 
         pw.printf("Success: Active admin set to component %s\n", mComponent.flattenToShortString());
@@ -253,7 +250,7 @@
     }
 
     private int runSetDeviceOwner(PrintWriter pw) {
-        parseArgs(/* canHaveName= */ true);
+        parseArgs();
         mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
 
         try {
@@ -277,14 +274,14 @@
     }
 
     private int runRemoveActiveAdmin(PrintWriter pw) {
-        parseArgs(/* canHaveName= */ false);
+        parseArgs();
         mService.forceRemoveActiveAdmin(mComponent, mUserId);
         pw.printf("Success: Admin removed %s\n", mComponent);
         return 0;
     }
 
     private int runSetProfileOwner(PrintWriter pw) {
-        parseArgs(/* canHaveName= */ true);
+        parseArgs();
         mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
 
         try {
@@ -340,13 +337,13 @@
     }
 
     private int runMarkProfileOwnerOnOrganizationOwnedDevice(PrintWriter pw) {
-        parseArgs(/* canHaveName= */ false);
+        parseArgs();
         mService.setProfileOwnerOnOrganizationOwnedDevice(mComponent, mUserId, true);
         pw.printf("Success\n");
         return 0;
     }
 
-    private void parseArgs(boolean canHaveName) {
+    private void parseArgs() {
         String opt;
         while ((opt = getNextOption()) != null) {
             if (USER_OPTION.equals(opt)) {
@@ -357,8 +354,6 @@
                 }
             } else if (DO_ONLY_OPTION.equals(opt)) {
                 mSetDoOnly = true;
-            } else if (canHaveName && NAME_OPTION.equals(opt)) {
-                mName = getNextArgRequired();
             } else {
                 throw new IllegalArgumentException("Unknown option: " + opt);
             }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 6196c49..9c9b363 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1287,8 +1287,8 @@
         bp.set_allocated_dest_path(&target);
         bp.set_allocated_source_subdir(&source);
         const auto metadata = bp.SerializeAsString();
-        bp.release_dest_path();
-        bp.release_source_subdir();
+        static_cast<void>(bp.release_dest_path());
+        static_cast<void>(bp.release_source_subdir());
         mdFileName = makeBindMdName();
         metadataFullPath = path::join(ifs.root, constants().mount, mdFileName);
         auto node = mIncFs->makeFile(ifs.control, metadataFullPath, 0444, idFromMetadata(metadata),
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 59d96d2..d9d3d62 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -474,8 +474,8 @@
         m.mutable_loader()->set_package_name("com.test");
         m.mutable_loader()->set_arguments("com.uri");
         const auto metadata = m.SerializeAsString();
-        m.mutable_loader()->release_arguments();
-        m.mutable_loader()->release_package_name();
+        static_cast<void>(m.mutable_loader()->release_arguments());
+        static_cast<void>(m.mutable_loader()->release_package_name());
         return {metadata.begin(), metadata.end()};
     }
     RawMetadata getStorageMetadata(const Control& control, std::string_view path) {
@@ -492,8 +492,8 @@
         bp.set_allocated_dest_path(&destPath);
         bp.set_allocated_source_subdir(&srcPath);
         const auto metadata = bp.SerializeAsString();
-        bp.release_source_subdir();
-        bp.release_dest_path();
+        static_cast<void>(bp.release_source_subdir());
+        static_cast<void>(bp.release_dest_path());
         return std::vector<char>(metadata.begin(), metadata.end());
     }
 };
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f921ccf..9e449ae 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -304,23 +304,23 @@
     private static final String SEARCH_MANAGER_SERVICE_CLASS =
             "com.android.server.search.SearchManagerService$Lifecycle";
     private static final String THERMAL_OBSERVER_CLASS =
-            "com.google.android.clockwork.ThermalObserver";
+            "com.android.clockwork.ThermalObserver";
     private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
             "com.android.clockwork.connectivity.WearConnectivityService";
     private static final String WEAR_POWER_SERVICE_CLASS =
             "com.android.clockwork.power.WearPowerService";
     private static final String HEALTH_SERVICE_CLASS =
-            "com.google.android.clockwork.healthservices.HealthService";
+            "com.android.clockwork.healthservices.HealthService";
     private static final String WEAR_SIDEKICK_SERVICE_CLASS =
             "com.google.android.clockwork.sidekick.SidekickService";
     private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS =
-            "com.google.android.clockwork.displayoffload.DisplayOffloadService";
+            "com.android.clockwork.displayoffload.DisplayOffloadService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
-            "com.google.android.clockwork.display.WearDisplayService";
+            "com.android.clockwork.display.WearDisplayService";
     private static final String WEAR_LEFTY_SERVICE_CLASS =
             "com.google.android.clockwork.lefty.WearLeftyService";
     private static final String WEAR_TIME_SERVICE_CLASS =
-            "com.google.android.clockwork.time.WearTimeService";
+            "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_GLOBAL_ACTIONS_SERVICE_CLASS =
             "com.android.clockwork.globalactions.GlobalActionsService";
     private static final String ACCOUNT_SERVICE_CLASS =
@@ -331,6 +331,8 @@
             "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
     private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
             "com.android.server.autofill.AutofillManagerService";
+    private static final String CREDENTIAL_MANAGER_SERVICE_CLASS =
+            "com.android.server.credentials.CredentialManagerService";
     private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
             "com.android.server.contentcapture.ContentCaptureManagerService";
     private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
@@ -373,8 +375,6 @@
             "com.android.server.searchui.SearchUiManagerService";
     private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
             "com.android.server.smartspace.SmartspaceManagerService";
-    private static final String CLOUDSEARCH_MANAGER_SERVICE_CLASS =
-            "com.android.server.cloudsearch.CloudSearchManagerService";
     private static final String DEVICE_IDLE_CONTROLLER_CLASS =
             "com.android.server.DeviceIdleController";
     private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -401,7 +401,8 @@
             "com.android.server.media.MediaCommunicationService";
     private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
             "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
-
+    private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS =
+            "com.android.server.healthconnect.HealthConnectManagerService";
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
     private static final String GAME_MANAGER_SERVICE_CLASS =
             "com.android.server.app.GameManagerService$Lifecycle";
@@ -762,15 +763,8 @@
             EventLog.writeEvent(EventLogTags.SYSTEM_SERVER_START,
                     mStartCount, mRuntimeStartUptime, mRuntimeStartElapsedTime);
 
-            //
-            // Default the timezone property to GMT if not set.
-            //
-            String timezoneProperty = SystemProperties.get("persist.sys.timezone");
-            if (!isValidTimeZoneId(timezoneProperty)) {
-                Slog.w(TAG, "persist.sys.timezone is not valid (" + timezoneProperty
-                        + "); setting to GMT.");
-                SystemProperties.set("persist.sys.timezone", "GMT");
-            }
+            // Set the device's time zone (a system property) if it is not set or is invalid.
+            SystemTimeZone.initializeTimeZoneSettingsIfRequired();
 
             // If the system has "persist.sys.language" and friends set, replace them with
             // "persist.sys.locale". Note that the default locale at this point is calculated
@@ -1857,12 +1851,6 @@
             mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
-            // CloudSearch manager service
-            // TODO: add deviceHasConfigString(context, R.string.config_defaultCloudSearchServices)
-            t.traceBegin("StartCloudSearchService");
-            mSystemServiceManager.startService(CLOUDSEARCH_MANAGER_SERVICE_CLASS);
-            t.traceEnd();
-
             t.traceBegin("InitConnectivityModuleConnector");
             try {
                 ConnectivityModuleConnector.getInstance().init(context);
@@ -2585,6 +2573,12 @@
             t.traceEnd();
         }
 
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CREDENTIALS)) {
+            t.traceBegin("StartCredentialManagerService");
+            mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
+        }
+
         // Translation manager service
         if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) {
             t.traceBegin("StartTranslationManagerService");
@@ -2743,6 +2737,10 @@
         mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
         t.traceEnd();
 
+        t.traceBegin("HealthConnectManagerService");
+        mSystemServiceManager.startService(HEALTHCONNECT_MANAGER_SERVICE_CLASS);
+        t.traceEnd();
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
index 525a931..c8d153a 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -35,6 +34,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.input.InputManagerInternal;
 
 final class SelectionToolbarManagerServiceImpl extends
         AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 1bcc3d1..1c6ba33 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -35,6 +35,7 @@
         "kotlin-reflect",
         "services.core",
         "servicestests-utils",
+        "servicestests-core-utils",
         "truth-prebuilt",
     ],
     platform_apis: true,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
new file mode 100644
index 0000000..faa2352
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.test
+
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.pm.Computer
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.PackageManagerService
+import com.android.server.pm.local.PackageManagerLocalImpl
+import com.android.server.pm.pkg.PackageState
+import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import kotlin.test.assertFailsWith
+
+class PackageManagerLocalSnapshotTest {
+
+    private val service = mockThrowOnUnmocked<PackageManagerService> {
+        @Suppress("DEPRECATION")
+        whenever(snapshotComputer(false)) { mockSnapshot() }
+    }
+
+    private val packageStateAll = mockPackageState("com.package.all")
+    private val packageStateUser0 = mockPackageState("com.package.zero")
+    private val packageStateUser10 = mockPackageState("com.package.ten")
+
+    @Test
+    fun unfiltered() {
+        val pmLocal = pmLocal()
+        val snapshot = pmLocal.withUnfilteredSnapshot()
+        val filteredOne: PackageManagerLocal.FilteredSnapshot
+        val filteredTwo: PackageManagerLocal.FilteredSnapshot
+        snapshot.use {
+            val packageStates = it.packageStates
+
+            // Check contents
+            assertThat(packageStates).containsExactly(
+                packageStateAll.packageName, packageStateAll,
+                packageStateUser0.packageName, packageStateUser0,
+                packageStateUser10.packageName, packageStateUser10,
+            )
+
+            // Check further calls get the same object
+            assertThat(it.packageStates).isSameInstanceAs(packageStates)
+
+            // Generate 3 filtered children (2 for the same caller, 1 for different)
+            filteredOne = it.filtered(1000, UserHandle.getUserHandleForUid(1000))
+            filteredTwo = it.filtered(1000, UserHandle.getUserHandleForUid(1000))
+            val filteredThree = it.filtered(20000, UserHandle.getUserHandleForUid(1001000))
+
+            // Check that siblings, even for the same input, are isolated
+            assertThat(filteredOne).isNotSameInstanceAs(filteredTwo)
+
+            assertThat(filteredOne.getPackageState(packageStateAll.packageName))
+                .isEqualTo(packageStateAll)
+            assertThat(filteredOne.getPackageState(packageStateUser0.packageName))
+                .isEqualTo(packageStateUser0)
+            assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
+
+            filteredThree.use {
+                val statesList = mutableListOf<PackageState>()
+                assertThat(it.forAllPackageStates { statesList += it })
+                assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+            }
+
+            // Call after child close, parent open fails
+            assertClosedFailure {
+                filteredThree.getPackageState(packageStateAll.packageName)
+            }
+
+            // Manually close first sibling and check that second still works
+            filteredOne.close()
+            assertThat(filteredTwo.getPackageState(packageStateAll.packageName))
+                .isEqualTo(packageStateAll)
+        }
+
+        // Call after close fails
+        assertClosedFailure { snapshot.packageStates }
+        assertClosedFailure { filteredOne.forAllPackageStates {} }
+        assertClosedFailure {
+            filteredTwo.getPackageState(packageStateAll.packageName)
+        }
+
+        // Check newly taken snapshot is different
+        assertThat(pmLocal.withUnfilteredSnapshot()).isNotSameInstanceAs(snapshot)
+    }
+
+    @Test
+    fun filtered() {
+        val pmLocal = pmLocal()
+        val snapshot = pmLocal.withFilteredSnapshot(1000, UserHandle.getUserHandleForUid(1000))
+        snapshot.use {
+            assertThat(it.getPackageState(packageStateAll.packageName))
+                .isEqualTo(packageStateAll)
+            assertThat(it.getPackageState(packageStateUser0.packageName))
+                .isEqualTo(packageStateUser0)
+            assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
+
+            val statesList = mutableListOf<PackageState>()
+            assertThat(it.forAllPackageStates { statesList += it })
+            assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+        }
+
+        // Call after close fails
+        assertClosedFailure {
+            snapshot.getPackageState(packageStateAll.packageName)
+        }
+
+        // Check newly taken snapshot is different
+        assertThat(pmLocal.withFilteredSnapshot()).isNotSameInstanceAs(snapshot)
+    }
+
+    private fun pmLocal(): PackageManagerLocal = PackageManagerLocalImpl(service)
+
+    private fun mockSnapshot() = mockThrowOnUnmocked<Computer> {
+        val packageStates = ArrayMap<String, PackageStateInternal>().apply {
+            put(packageStateAll.packageName, packageStateAll)
+            put(packageStateUser0.packageName, packageStateUser0)
+            put(packageStateUser10.packageName, packageStateUser10)
+        }
+        whenever(this.packageStates) { packageStates }
+        whenever(getPackageStateFiltered(anyString(), anyInt(), anyInt())) {
+            packageStates[arguments[0]]?.takeUnless {
+                shouldFilterApplication(it, arguments[1] as Int, arguments[2] as Int)
+            }
+        }
+
+        whenever(
+            shouldFilterApplication(any(PackageStateInternal::class.java), anyInt(), anyInt())
+        ) {
+            val packageState = arguments[0] as PackageState
+            val user = arguments[2] as Int
+
+            when (packageState) {
+                packageStateAll -> false
+                packageStateUser0 -> user != 0
+                packageStateUser10 -> user != 10
+                else -> true
+            }
+        }
+    }
+
+    private fun mockPackageState(packageName: String) = mockThrowOnUnmocked<PackageStateInternal> {
+        whenever(this.packageName) { packageName }
+        whenever(toString()) { packageName }
+    }
+
+    private fun assertClosedFailure(block: () -> Unit) =
+        assertFailsWith(IllegalStateException::class, block)
+            .run { assertThat(message).isEqualTo("Snapshot already closed") }
+}
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 5361041..1f66a11 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
@@ -17,13 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.Intent
-import android.content.pm.ApplicationInfo
-import android.content.pm.ConfigurationInfo
-import android.content.pm.FeatureGroupInfo
-import android.content.pm.FeatureInfo
-import android.content.pm.PackageManager
-import android.content.pm.SigningDetails
-import com.android.server.pm.pkg.parsing.ParsingPackage
+import android.content.pm.*
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -33,18 +27,7 @@
 import com.android.internal.R
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
-import com.android.server.pm.pkg.component.ParsedAttributionImpl
-import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
-import com.android.server.pm.pkg.component.ParsedPermissionImpl
-import com.android.server.pm.pkg.component.ParsedProcessImpl
-import com.android.server.pm.pkg.component.ParsedProviderImpl
-import com.android.server.pm.pkg.component.ParsedServiceImpl
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
+import com.android.server.pm.pkg.component.*
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
@@ -65,6 +48,7 @@
         "addMimeGroupsFromComponent",
         "assignDerivedFields",
         "assignDerivedFields2",
+        "makeImmutable",
         "buildFakeForDeletion",
         "buildAppClassNamesByProcess",
         "capPermissionPriorities",
@@ -103,6 +87,7 @@
         "getRequestedPermissions",
         // Tested through asSplit
         "asSplit",
+        "getSplits",
         "getSplitNames",
         "getSplitCodePaths",
         "getSplitRevisionCodes",
@@ -175,9 +160,9 @@
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
         AndroidPackage::getSharedUserLabel,
-        AndroidPackage::getSdkLibName,
+        AndroidPackage::getSdkLibraryName,
         AndroidPackage::getSdkLibVersionMajor,
-        AndroidPackage::getStaticSharedLibName,
+        AndroidPackage::getStaticSharedLibraryName,
         AndroidPackage::getStaticSharedLibVersion,
         AndroidPackage::getTargetSandboxVersion,
         AndroidPackage::getTargetSdkVersion,
@@ -219,6 +204,7 @@
         AndroidPackage::isNativeLibraryRootRequiresIsa,
         AndroidPackage::isOdm,
         AndroidPackage::isOem,
+        AndroidPackage::isOnBackInvokedCallbackEnabled,
         AndroidPackage::isOverlay,
         AndroidPackage::isOverlayIsStatic,
         AndroidPackage::isPartiallyDirectBootAware,
@@ -279,7 +265,7 @@
         adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
         getSetByValue(
             AndroidPackage::areAttributionsUserVisible,
-            ParsingPackage::setAttributionsAreUserVisible,
+            PackageImpl::setAttributionsAreUserVisible,
             true
         ),
         getSetByValue2(
@@ -513,11 +499,6 @@
             }
         ),
         getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")),
-        getSetByValue(
-            AndroidPackage::isOnBackInvokedCallbackEnabled,
-            ParsingPackage::setOnBackInvokedCallbackEnabled,
-            true
-        )
     )
 
     override fun initialObject() = PackageImpl.forParsing(
@@ -554,16 +535,20 @@
             SparseArray<IntArray>().apply {
                 put(0, intArrayOf(-1))
                 put(1, intArrayOf(0))
+                put(2, intArrayOf(1))
             }
         )
         .setSplitHasCode(0, true)
         .setSplitHasCode(1, false)
         .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
         .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
-
         .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
         .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
 
+    override fun finalizeObject(parcelable: Parcelable) {
+        (parcelable as PackageImpl).hideAsParsed().hideAsFinal()
+    }
+
     override fun extraAssertions(before: Parcelable, after: Parcelable) {
         super.extraAssertions(before, after)
         after as PackageImpl
@@ -600,9 +585,10 @@
 
         expect.that(after.splitDependencies).isNotNull()
         after.splitDependencies?.let {
-            expect.that(it.size()).isEqualTo(2)
+            expect.that(it.size()).isEqualTo(3)
             expect.that(it.get(0)).asList().containsExactly(-1)
             expect.that(it.get(1)).asList().containsExactly(0)
+            expect.that(it.get(2)).asList().containsExactly(1)
         }
 
         expect.that(after.usesSdkLibraries).containsExactly("testSdk")
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
index e16a187..37bb935 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
@@ -293,12 +293,21 @@
         first?.let { property(it) } == second?.let { property(it) }
     }
 
-    @Test
-    fun valueComparison() {
+    fun buildBefore(): Pair<List<Param>, Parcelable> {
         val params = baseParams.mapNotNull(::buildParams) + extraParams().filterNotNull()
         val before = initialObject()
 
         params.forEach { it.setFunction(arrayOf(before, it.value())) }
+        finalizeObject(before)
+        return params to before
+    }
+
+    protected open fun finalizeObject(parcelable: Parcelable) {
+    }
+
+    @Test
+    fun valueComparison() {
+        val (params, before) = buildBefore()
 
         val parcel = Parcel.obtain()
         writeToParcel(parcel, before)
@@ -307,6 +316,15 @@
 
         parcel.setDataPosition(0)
 
+        val baseline = initialObject()
+        finalizeObject(baseline)
+
+        val baselineParcel = Parcel.obtain()
+        writeToParcel(baselineParcel, baseline)
+
+        // Check that something substantial actually changed in the test object
+        expect.that(parcel.dataSize()).isGreaterThan(baselineParcel.dataSize())
+
         val after = creator.createFromParcel(parcel)
 
         expect.withMessage("Mismatched write and read data sizes")
@@ -314,6 +332,7 @@
             .isEqualTo(dataSize)
 
         parcel.recycle()
+        baselineParcel.recycle()
 
         runAssertions(params, before, after)
     }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
new file mode 100644
index 0000000..8a855e5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -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.server.pm.test.pkg
+
+import android.content.Intent
+import android.content.pm.overlay.OverlayPaths
+import android.content.pm.PackageManager
+import android.content.pm.PathPermission
+import android.content.pm.SharedLibraryInfo
+import android.content.pm.VersionedPackage
+import android.os.PatternMatcher
+import android.util.ArraySet
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.PackageSettingBuilder
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+import com.android.server.pm.pkg.PackageStateImpl
+import com.android.server.pm.pkg.PackageUserState
+import com.android.server.pm.pkg.PackageUserStateImpl
+import com.android.server.pm.pkg.component.ParsedActivity
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentation
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermission
+import com.android.server.pm.pkg.component.ParsedPermissionGroup
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcess
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProvider
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedService
+import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest
+import com.google.common.truth.Expect
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import kotlin.contracts.ExperimentalContracts
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.KType
+import kotlin.reflect.full.isSubtypeOf
+import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.full.starProjectedType
+
+class PackageStateTest {
+
+    companion object {
+        private val IGNORED_TYPES = listOf(
+                "java.io.File",
+                "java.lang.Boolean",
+                "java.lang.Byte",
+                "java.lang.CharSequence",
+                "java.lang.Character",
+                "java.lang.Double",
+                "java.lang.Float",
+                "java.lang.Integer",
+                "java.lang.Long",
+                "java.lang.Short",
+                "java.lang.String",
+                "java.lang.Void",
+        )
+        // STOPSHIP: Remove these and fix the implementations
+        private val IGNORED_FUNCTIONS = listOf(
+            ParsedActivity::getIntents,
+            ParsedActivity::getKnownActivityEmbeddingCerts,
+            ParsedActivity::getProperties,
+            ParsedInstrumentation::getIntents,
+            ParsedInstrumentation::getIntents,
+            ParsedInstrumentation::getProperties,
+            ParsedInstrumentation::getProperties,
+            ParsedPermission::getIntents,
+            ParsedPermission::getProperties,
+            ParsedPermissionGroup::getIntents,
+            ParsedPermissionGroup::getProperties,
+            ParsedProcess::getAppClassNamesByPackage,
+            ParsedProvider::getIntents,
+            ParsedProvider::getPathPermissions,
+            ParsedProvider::getProperties,
+            ParsedProvider::getUriPermissionPatterns,
+            ParsedService::getIntents,
+            ParsedService::getProperties,
+            Intent::getCategories,
+            PackageUserState::getDisabledComponents,
+            PackageUserState::getEnabledComponents,
+            PackageUserState::getSharedLibraryOverlayPaths,
+            OverlayPaths::getOverlayPaths,
+            OverlayPaths::getResourceDirs,
+        )
+    }
+
+    @get:Rule
+    val tempFolder = TemporaryFolder()
+
+    @get:Rule
+    val expect = Expect.create()
+
+    private val collectionType = MutableCollection::class.starProjectedType
+    private val mapType = Map::class.starProjectedType
+
+    @OptIn(ExperimentalContracts::class)
+    @Test
+    fun collectionImmutability() {
+        val seenTypes = mutableSetOf<KType>()
+        val (_, pkg) = AndroidPackageTest().buildBefore()
+        val packageState = PackageSettingBuilder()
+            .setPackage(pkg as AndroidPackage)
+            .setCodePath(tempFolder.newFile().path)
+            .build()
+
+        fillMissingData(packageState, pkg as PackageImpl)
+
+        visitType(seenTypes, emptyList(), PackageStateImpl.copy(packageState),
+            PackageState::class.starProjectedType)
+        visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType)
+        visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0),
+                PackageUserState::class.starProjectedType)
+
+        // Don't check empties for defaults since their collections will always be empty
+        visitType(seenTypes, emptyList(), PackageUserState.DEFAULT,
+                PackageUserState::class.starProjectedType, enforceNonEmpty = false)
+
+        // Check that some minimum number of functions were validated,
+        // in case the type checking breaks somehow
+        expect.that(seenTypes.size).isGreaterThan(10)
+    }
+
+    /**
+     * Fill fields in [PackageState] and its children that are not filled by [AndroidPackageTest].
+     * Real objects and real invocations of the live APIs are necessary to ensure that the test
+     * mirrors real device behavior.
+     */
+    private fun fillMissingData(pkgSetting: PackageSetting, pkg: PackageImpl) {
+        pkgSetting.addUsesLibraryFile("usesLibraryFile")
+
+        val sharedLibraryDependency = listOf(SharedLibraryInfo(
+            "pathDependency",
+            "packageNameDependency",
+            listOf(tempFolder.newFile().path),
+            "nameDependency",
+            1,
+            0,
+            VersionedPackage("versionedPackage0Dependency", 1),
+            listOf(VersionedPackage("versionedPackage1Dependency", 2)),
+            emptyList(),
+            false
+        ))
+
+        pkgSetting.addUsesLibraryInfo(SharedLibraryInfo(
+            "path",
+            "packageName",
+            listOf(tempFolder.newFile().path),
+            "name",
+            1,
+            0,
+            VersionedPackage("versionedPackage0", 1),
+            listOf(VersionedPackage("versionedPackage1", 2)),
+            sharedLibraryDependency,
+            false
+        ))
+        pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType"))
+        pkgSetting.getOrCreateUserState(0).apply {
+            setEnabledComponents(ArraySet<String>().apply { add("com.test.EnabledComponent") })
+            setDisabledComponents(ArraySet<String>().apply { add("com.test.DisabledComponent") })
+            setSharedLibraryOverlayPaths("sharedLibrary",
+                OverlayPaths.Builder().addApkPath("/test/overlay.apk").build())
+        }
+
+        val property = PackageManager.Property("propertyName", 1, "com.test", null)
+        listOf(
+            pkg.activities,
+            pkg.receivers,
+            pkg.providers,
+            pkg.services,
+            pkg.instrumentations,
+            pkg.permissions,
+            pkg.permissionGroups
+        ).map { it.first() as ParsedComponentImpl }
+            .forEach {
+                it.addIntent(ParsedIntentInfoImpl())
+                it.addProperty(property)
+            }
+
+        (pkg.activities.first() as ParsedActivityImpl).knownActivityEmbeddingCerts =
+            setOf("TESTEMBEDDINGCERT")
+
+        (pkg.permissions.first() as ParsedPermissionImpl).knownCerts = setOf("TESTEMBEDDINGCERT")
+
+        (pkg.providers.first() as ParsedProviderImpl).apply {
+            addPathPermission(PathPermission("pattern", PatternMatcher.PATTERN_LITERAL,
+                "readPermission", "writerPermission"))
+            addUriPermissionPattern(PatternMatcher("*", PatternMatcher.PATTERN_LITERAL))
+        }
+
+        (pkg.processes.values.first() as ParsedProcessImpl).apply {
+            deniedPermissions = setOf("deniedPermission")
+            putAppClassNameForPackage("package", "className")
+        }
+    }
+
+    private fun visitType(
+        seenTypes: MutableSet<KType>,
+        parentChain: List<String>,
+        impl: Any,
+        type: KType,
+        enforceNonEmpty: Boolean = true
+    ) {
+        if (!seenTypes.add(type)) return
+        val kClass = type.classifier as KClass<*>
+        val qualifiedName = kClass.qualifiedName!!
+        if (IGNORED_TYPES.contains(qualifiedName)) return
+
+        val newChain = parentChain + kClass.simpleName!!
+        val newChainText = newChain.joinToString()
+
+        val filteredFunctions = kClass.memberFunctions
+            .filter {
+                // Size 1 because the impl receiver counts as a parameter
+                it.parameters.size == 1
+            }
+            .filterNot(IGNORED_FUNCTIONS::contains)
+
+        filteredFunctions.filter { it.returnType.isSubtypeOf(collectionType) }
+                .forEach {
+                    val collection = it.call(impl)
+                    if (collection as? MutableCollection<*> == null) {
+                        expect.withMessage("Method $newChainText ${it.name} cannot return null")
+                            .fail()
+                        return@forEach
+                    }
+
+                    val value = try {
+                        if (AndroidPackage::getSplits == it) {
+                            // The base split is defined to never have any dependencies,
+                            // so force the visitor to use the split at index 1 instead of 0.
+                            collection.last()
+                        } else {
+                            collection.first()
+                        }
+                    } catch (e: Exception) {
+                        if (enforceNonEmpty) {
+                            expect.withMessage("Method $newChainText ${it.name} returns empty")
+                                .that(e)
+                                .isNull()
+                            return@forEach
+                        } else null
+                    }
+
+                    if (value != null) {
+                        it.returnType.arguments.forEach {
+                            visitType(seenTypes, newChain, value, it.type!!)
+                        }
+                    }
+
+                    // Must test clear last in case it works and actually clears the collection
+                    expectUnsupported(newChain, it) { collection.clear() }
+                }
+        filteredFunctions.filter { it.returnType.isSubtypeOf(mapType) }
+                .forEach {
+                    val map = it.call(impl)
+                    if (map as? MutableMap<*, *> == null) {
+                        expect.withMessage("Method $newChainText ${it.name} cannot return null")
+                            .fail()
+                        return@forEach
+                    }
+
+                    val entry = try {
+                        map.entries.stream().findFirst().get()!!
+                    } catch (e: Exception) {
+                        expect.withMessage("Method $newChainText ${it.name} returns empty")
+                                .that(e)
+                                .isNull()
+                        return@forEach
+                    }
+
+                    visitType(seenTypes, newChain, entry.key!!, it.returnType.arguments[0].type!!)
+                    visitType(seenTypes, newChain, entry.value!!, it.returnType.arguments[1].type!!)
+
+                    // Must test clear last in case it works and actually clears the map
+                    expectUnsupported(newChain, it) { map.clear() }
+                }
+    }
+
+    private fun expectUnsupported(
+            parentChain: List<String>,
+            function: KFunction<*>,
+            block: () -> Unit
+    ) {
+        val exception = try {
+            block()
+            null
+        } catch (e: UnsupportedOperationException) {
+            e
+        }
+
+        expect.withMessage("Method ${parentChain.joinToString()} $function doesn't throw")
+                .that(exception)
+                .isNotNull()
+    }
+}
\ No newline at end of file
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 d9c622d..e2b25ef 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -52,6 +52,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE;
@@ -111,6 +112,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -166,10 +168,12 @@
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
+import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.SystemService;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.tare.AlarmManagerEconomicPolicy;
 import com.android.server.tare.EconomyManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
 
@@ -323,7 +327,12 @@
         }
 
         @Override
-        void setKernelTimezone(int minutesWest) {
+        void setKernelTimeZoneOffset(int utcOffsetMillis) {
+            // Do nothing.
+        }
+
+        @Override
+        void syncKernelTimeZoneOffset() {
             // Do nothing.
         }
 
@@ -338,10 +347,6 @@
         }
 
         @Override
-        void setKernelTime(long millis) {
-        }
-
-        @Override
         int getSystemUiUid(PackageManagerInternal unused) {
             return SYSTEM_UI_UID;
         }
@@ -353,7 +358,18 @@
         }
 
         @Override
-        long getElapsedRealtime() {
+        void initializeTimeIfRequired() {
+            // No-op
+        }
+
+        @Override
+        void setCurrentTimeMillis(long unixEpochMillis,
+                @TimeConfidence int confidence, String logMsg) {
+            mNowRtcTest = unixEpochMillis;
+        }
+
+        @Override
+        long getElapsedRealtimeMillis() {
             return mNowElapsedTest;
         }
 
@@ -643,7 +659,8 @@
     }
 
     private void setTareEnabled(boolean enabled) {
-        when(mEconomyManagerInternal.isEnabled()).thenReturn(enabled);
+        when(mEconomyManagerInternal.isEnabled(eq(AlarmManagerEconomicPolicy.POLICY_ALARM)))
+                .thenReturn(enabled);
         mService.mConstants.onTareEnabledStateChanged(enabled);
     }
 
@@ -3421,12 +3438,13 @@
     public void setTimeZoneImpl() {
         final long durationMs = 20000L;
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
-        mService.setTimeZoneImpl("UTC");
+        mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH, "AlarmManagerServiceTest");
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
                 isNull(), bundleCaptor.capture());
         assertEquals(Intent.ACTION_TIMEZONE_CHANGED, intentCaptor.getValue().getAction());
+        assertEquals("UTC", intentCaptor.getValue().getStringExtra(Intent.EXTRA_TIMEZONE));
         final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
         assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration());
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
new file mode 100644
index 0000000..09df96f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+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.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.am.ActivityManagerService.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+/**
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
+ */
+public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+
+    private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
+
+    private final Display mDefaultDisplay = validDisplay(DEFAULT_DISPLAY);
+
+    @Mock private Context mContext;
+    @Mock private DisplayManager mDisplayManager;
+
+    private Injector mInjector;
+
+    @Before
+    public void setFixture() {
+        mInjector = new Injector(mContext);
+
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+    }
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(UserManager.class);
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_notSupported() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(false);
+
+        int [] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDisplaysAtAll() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays();
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_defaultDisplayOnly() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(mDefaultDisplay);
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDefaultDisplay() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(validDisplay(42));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(mDefaultDisplay, validDisplay(42), invalidDisplay(108));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNotNull();
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(42);
+    }
+
+    // Extra test to make sure the array is properly copied...
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed_invalidFirst() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(invalidDisplay(108), mDefaultDisplay, validDisplay(42));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(42);
+    }
+
+    private Display validDisplay(int displayId) {
+        return mockDisplay(displayId, /* valid= */ true);
+    }
+
+    private Display invalidDisplay(int displayId) {
+        return mockDisplay(displayId, /* valid= */ false);
+    }
+
+    private Display mockDisplay(int displayId, boolean valid) {
+        Display display = mock(Display.class);
+
+        when(display.getDisplayId()).thenReturn(displayId);
+        when(display.isValid()).thenReturn(valid);
+
+        return display;
+    }
+
+    private void mockGetDisplays(Display... displays) {
+        Log.d(TAG, "mockGetDisplays(): " + Arrays.toString(displays));
+        when(mDisplayManager.getDisplays()).thenReturn(displays);
+    }
+
+    private void mockUmIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
+        Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
+        doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
new file mode 100644
index 0000000..86915da
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
+import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED;
+import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
+import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
+import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+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.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.class)
+public class BroadcastQueueModernImplTest {
+    private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
+
+    @Mock ActivityManagerService mAms;
+    @Mock ProcessRecord mProcess;
+
+    @Mock BroadcastProcessQueue mQueue1;
+    @Mock BroadcastProcessQueue mQueue2;
+    @Mock BroadcastProcessQueue mQueue3;
+    @Mock BroadcastProcessQueue mQueue4;
+
+    HandlerThread mHandlerThread;
+
+    BroadcastConstants mConstants;
+    BroadcastQueueModernImpl mImpl;
+
+    BroadcastProcessQueue mHead;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(getClass().getSimpleName());
+        mHandlerThread.start();
+
+        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+        mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+                mConstants, mConstants);
+
+        doReturn(1L).when(mQueue1).getRunnableAt();
+        doReturn(2L).when(mQueue2).getRunnableAt();
+        doReturn(3L).when(mQueue3).getRunnableAt();
+        doReturn(4L).when(mQueue4).getRunnableAt();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    private static void assertOrphan(BroadcastProcessQueue queue) {
+        assertNull(queue.runnableAtNext);
+        assertNull(queue.runnableAtPrev);
+    }
+
+    private static void assertRunnableList(@NonNull List<BroadcastProcessQueue> expected,
+            @NonNull BroadcastProcessQueue actualHead) {
+        BroadcastProcessQueue test = actualHead;
+        final int N = expected.size();
+        for (int i = 0; i < N; i++) {
+            final BroadcastProcessQueue expectedPrev = (i > 0) ? expected.get(i - 1) : null;
+            final BroadcastProcessQueue expectedTest = expected.get(i);
+            final BroadcastProcessQueue expectedNext = (i < N - 1) ? expected.get(i + 1) : null;
+
+            assertEquals("prev", expectedPrev, test.runnableAtPrev);
+            assertEquals("test", expectedTest, test);
+            assertEquals("next", expectedNext, test.runnableAtNext);
+
+            test = test.runnableAtNext;
+        }
+        if (N == 0) {
+            assertNull(actualHead);
+        }
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent) {
+        return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
+    }
+
+    private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) {
+        return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) {
+        return makeBroadcastRecord(intent, options,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
+            List receivers, boolean ordered) {
+        return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
+                null, null, null, AppOpsManager.OP_NONE, options, receivers, null,
+                Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
+                false, null, false, null);
+    }
+
+    @Test
+    public void testRunnableList_Simple() {
+        assertRunnableList(List.of(), mHead);
+
+        mHead = insertIntoRunnableList(mHead, mQueue1);
+        assertRunnableList(List.of(mQueue1), mHead);
+
+        mHead = removeFromRunnableList(mHead, mQueue1);
+        assertRunnableList(List.of(), mHead);
+    }
+
+    @Test
+    public void testRunnableList_InsertLast() {
+        mHead = insertIntoRunnableList(mHead, mQueue1);
+        mHead = insertIntoRunnableList(mHead, mQueue2);
+        mHead = insertIntoRunnableList(mHead, mQueue3);
+        mHead = insertIntoRunnableList(mHead, mQueue4);
+        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead);
+    }
+
+    @Test
+    public void testRunnableList_InsertFirst() {
+        mHead = insertIntoRunnableList(mHead, mQueue4);
+        mHead = insertIntoRunnableList(mHead, mQueue3);
+        mHead = insertIntoRunnableList(mHead, mQueue2);
+        mHead = insertIntoRunnableList(mHead, mQueue1);
+        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead);
+    }
+
+    @Test
+    public void testRunnableList_InsertMiddle() {
+        mHead = insertIntoRunnableList(mHead, mQueue1);
+        mHead = insertIntoRunnableList(mHead, mQueue3);
+        mHead = insertIntoRunnableList(mHead, mQueue2);
+        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3), mHead);
+    }
+
+    @Test
+    public void testRunnableList_Remove() {
+        mHead = insertIntoRunnableList(mHead, mQueue1);
+        mHead = insertIntoRunnableList(mHead, mQueue2);
+        mHead = insertIntoRunnableList(mHead, mQueue3);
+        mHead = insertIntoRunnableList(mHead, mQueue4);
+
+        mHead = removeFromRunnableList(mHead, mQueue3);
+        assertRunnableList(List.of(mQueue1, mQueue2, mQueue4), mHead);
+
+        mHead = removeFromRunnableList(mHead, mQueue1);
+        assertRunnableList(List.of(mQueue2, mQueue4), mHead);
+
+        mHead = removeFromRunnableList(mHead, mQueue4);
+        assertRunnableList(List.of(mQueue2), mHead);
+
+        mHead = removeFromRunnableList(mHead, mQueue2);
+        assertRunnableList(List.of(), mHead);
+
+        // Verify all links cleaned up during removal
+        assertOrphan(mQueue1);
+        assertOrphan(mQueue2);
+        assertOrphan(mQueue3);
+        assertOrphan(mQueue4);
+    }
+
+    @Test
+    public void testProcessQueue_Complex() {
+        BroadcastProcessQueue red = mImpl.getOrCreateProcessQueue(PACKAGE_RED, TEST_UID);
+        BroadcastProcessQueue green = mImpl.getOrCreateProcessQueue(PACKAGE_GREEN, TEST_UID);
+        BroadcastProcessQueue blue = mImpl.getOrCreateProcessQueue(PACKAGE_BLUE, TEST_UID);
+
+        assertEquals(PACKAGE_RED, red.processName);
+        assertEquals(PACKAGE_GREEN, green.processName);
+        assertEquals(PACKAGE_BLUE, blue.processName);
+
+        // Verify that removing middle queue works
+        mImpl.removeProcessQueue(PACKAGE_GREEN, TEST_UID);
+        assertEquals(red, mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+        assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+        // Verify that removing head queue works
+        mImpl.removeProcessQueue(PACKAGE_RED, TEST_UID);
+        assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+        assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+        // Verify that removing last queue works
+        mImpl.removeProcessQueue(PACKAGE_BLUE, TEST_UID);
+        assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
+        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+
+        // Verify that removing missing doesn't crash
+        mImpl.removeProcessQueue(PACKAGE_YELLOW, TEST_UID);
+
+        // Verify that we can start all over again safely
+        BroadcastProcessQueue yellow = mImpl.getOrCreateProcessQueue(PACKAGE_YELLOW, TEST_UID);
+        assertEquals(yellow, mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
+    }
+
+    /**
+     * Empty queue isn't runnable.
+     */
+    @Test
+    public void testRunnableAt_Empty() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        assertFalse(queue.isRunnable());
+        assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+    }
+
+    /**
+     * Queue with a "normal" broadcast is runnable at different times depending
+     * on process cached state; when cached it's delayed by some amount.
+     */
+    @Test
+    public void testRunnableAt_Normal() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+
+        queue.setProcessCached(false);
+        final long notCachedRunnableAt = queue.getRunnableAt();
+        queue.setProcessCached(true);
+        final long cachedRunnableAt = queue.getRunnableAt();
+        assertTrue(cachedRunnableAt > notCachedRunnableAt);
+        assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
+    }
+
+    /**
+     * Queue with foreground broadcast is always runnable immediately,
+     * regardless of process cached state.
+     */
+    @Test
+    public void testRunnableAt_Foreground() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+
+        queue.setProcessCached(false);
+        assertTrue(queue.isRunnable());
+        assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+
+        queue.setProcessCached(true);
+        assertTrue(queue.isRunnable());
+        assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+    }
+
+    /**
+     * Queue with ordered broadcast is runnable only once we've made enough
+     * progress on earlier blocking items.
+     */
+    @Test
+    public void testRunnableAt_Ordered() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+
+        assertFalse(queue.isRunnable());
+        assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
+
+        // Bumping past barrier makes us now runnable
+        airplaneRecord.terminalCount++;
+        queue.invalidateRunnableAt();
+        assertTrue(queue.isRunnable());
+        assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
+    }
+
+    /**
+     * Queue with too many pending broadcasts is runnable.
+     */
+    @Test
+    public void testRunnableAt_Huge() {
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+
+        mConstants.MAX_PENDING_BROADCASTS = 128;
+        queue.invalidateRunnableAt();
+        assertTrue(queue.getRunnableAt() > airplaneRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+
+        mConstants.MAX_PENDING_BROADCASTS = 1;
+        queue.invalidateRunnableAt();
+        assertTrue(queue.getRunnableAt() == airplaneRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
+    }
+
+    /**
+     * Verify that sending a broadcast that removes any matching pending
+     * broadcasts is applied as expected.
+     */
+    @Test
+    public void testRemoveMatchingFilter() {
+        final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+        final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
+        optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
+        final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+        final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
+        optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+
+        // While we're here, give our health check some test coverage
+        mImpl.checkHealthLocked();
+
+        // Marching through the queue we should only have one SCREEN_OFF
+        // broadcast, since that's the last state we dispatched
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
+        assertTrue(queue.isEmpty());
+    }
+}
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 6bf102a..076fce9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,22 +16,43 @@
 
 package com.android.server.am;
 
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.server.am.BroadcastProcessQueue.reasonToString;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+
+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.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.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 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.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -39,25 +60,34 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.PowerExemptionManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.DropBoxManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService.Injector;
 import com.android.server.appop.AppOpsService;
 import com.android.server.wm.ActivityTaskManagerService;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -65,14 +95,25 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 import org.mockito.ArgumentMatcher;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.UnaryOperator;
 
 /**
  * Common tests for {@link BroadcastQueue} implementations.
@@ -103,16 +144,36 @@
     @Mock
     private ProcessList mProcessList;
     @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
     private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
 
     private ActivityManagerService mAms;
     private BroadcastQueue mQueue;
 
     /**
+     * When enabled {@link ActivityManagerService#startProcessLocked} will fail
+     * by returning {@code null}; otherwise it will spawn a new mock process.
+     */
+    private boolean mFailStartProcess;
+
+    /**
      * Map from PID to registered registered runtime receivers.
      */
     private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();
 
+    /**
+     * Collection of all active processes during current test run.
+     */
+    private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
+
+    /**
+     * Collection of scheduled broadcasts, in the order they were dispatched.
+     */
+    private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
+
     @Parameters(name = "impl={0}")
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
@@ -132,24 +193,36 @@
         mHandlerThread.start();
         mNextPid = new AtomicInteger(100);
 
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
         doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+        doAnswer((invocation) -> {
+            return getUidForPackage(invocation.getArgument(0));
+        }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
 
         final ActivityManagerService realAms = new ActivityManagerService(
                 new TestInjector(mContext), mServiceThreadRule.getThread());
         realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+        realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
         realAms.mPackageManagerInt = mPackageManagerInt;
+        realAms.mUsageStatsService = mUsageStatsManagerInt;
+        realAms.mProcessesReady = true;
         mAms = spy(realAms);
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting startProcessLocked() for "
                     + Arrays.toString(invocation.getArguments()));
+            if (mFailStartProcess) {
+                return null;
+            }
             final String processName = invocation.getArgument(0);
             final ApplicationInfo ai = invocation.getArgument(1);
-            final ProcessRecord res = makeActiveProcessRecord(ai, processName);
+            final ProcessRecord res = makeActiveProcessRecord(ai, processName,
+                    ProcessBehavior.NORMAL, UnaryOperator.identity());
             mHandlerThread.getThreadHandler().post(() -> {
                 synchronized (mAms) {
                     mQueue.onApplicationAttachedLocked(res);
@@ -158,19 +231,35 @@
             return res;
         }).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
                 any(), anyInt(), anyBoolean(), anyBoolean());
+        doAnswer((invocation) -> {
+            final String processName = invocation.getArgument(0);
+            final int uid = invocation.getArgument(1);
+            for (ProcessRecord r : mActiveProcesses) {
+                if (Objects.equals(r.processName, processName) && r.uid == uid) {
+                    return r;
+                }
+            }
+            return null;
+        }).when(mAms).getProcessRecordLocked(any(), anyInt());
+        doNothing().when(mAms).appNotResponding(any(), any());
 
         final BroadcastConstants constants = new BroadcastConstants(
                 Settings.Global.BROADCAST_FG_CONSTANTS);
+        constants.TIMEOUT = 100;
+        constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
             public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+                // Ignored
                 return false;
             }
             public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+                // Ignored
                 return false;
             }
         };
-        final BroadcastHistory emptyHistory = new BroadcastHistory() {
+        final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
             public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+                // Ignored
             }
         };
 
@@ -180,12 +269,24 @@
                     ProcessList.SCHED_GROUP_DEFAULT);
         } else if (mImpl == Impl.MODERN) {
             mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-                    constants, emptySkipPolicy, emptyHistory);
+                    constants, constants, emptySkipPolicy, emptyHistory);
         } else {
             throw new UnsupportedOperationException();
         }
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+
+        // Verify that all processes have finished handling broadcasts
+        for (ProcessRecord app : mActiveProcesses) {
+            assertTrue(app.toShortString(), app.mReceivers.numberOfCurReceivers() == 0);
+            assertTrue(app.toShortString(), mQueue.getPreferredSchedulingGroupLocked(app)
+                    == ProcessList.SCHED_GROUP_UNDEFINED);
+        }
+    }
+
     private class TestInjector extends Injector {
         TestInjector(Context context) {
             super(context);
@@ -207,21 +308,72 @@
         }
     }
 
-    private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
-        final ApplicationInfo ai = makeApplicationInfo(packageName);
-        return makeActiveProcessRecord(ai, ai.processName);
+    /**
+     * Helper that leverages try-with-resources to pause dispatch of
+     * {@link #mHandlerThread} until released.
+     */
+    private class SyncBarrier implements AutoCloseable {
+        private final int mToken;
+
+        public SyncBarrier() {
+            mToken = mHandlerThread.getLooper().getQueue().postSyncBarrier();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mHandlerThread.getLooper().getQueue().removeSyncBarrier(mToken);
+        }
     }
 
-    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName)
-            throws Exception {
-        final ProcessRecord r = new ProcessRecord(mAms, ai, processName, ai.uid);
-        r.setPid(mNextPid.getAndIncrement());
+    private enum ProcessBehavior {
+        /** Process broadcasts normally */
+        NORMAL,
+        /** Wedge and never confirm broadcast receipt */
+        WEDGE,
+        /** Process broadcast by requesting abort */
+        ABORT,
+        /** Appear to behave completely dead */
+        DEAD,
+    }
 
-        final IApplicationThread thread = mock(IApplicationThread.class);
+    private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
+        return makeActiveProcessRecord(packageName, packageName, ProcessBehavior.NORMAL,
+                UserHandle.USER_SYSTEM);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName,
+            ProcessBehavior behavior) throws Exception {
+        return makeActiveProcessRecord(packageName, packageName, behavior, UserHandle.USER_SYSTEM);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName, String processName,
+            ProcessBehavior behavior, int userId) throws Exception {
+        final ApplicationInfo ai = makeApplicationInfo(packageName, processName, userId);
+        return makeActiveProcessRecord(ai, ai.processName, behavior,
+                UnaryOperator.identity());
+    }
+
+    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
+            ProcessBehavior behavior, UnaryOperator<Bundle> extrasOperator) throws Exception {
+        final boolean wedge = (behavior == ProcessBehavior.WEDGE);
+        final boolean abort = (behavior == ProcessBehavior.ABORT);
+        final boolean dead = (behavior == ProcessBehavior.DEAD);
+
+        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
+        r.setPid(mNextPid.getAndIncrement());
+        mActiveProcesses.add(r);
+
+        final IApplicationThread thread;
+        if (dead) {
+            thread = mock(IApplicationThread.class, (invocation) -> {
+                throw new DeadObjectException();
+            });
+        } else {
+            thread = mock(IApplicationThread.class);
+        }
         final IBinder threadBinder = new Binder();
         doReturn(threadBinder).when(thread).asBinder();
         r.makeActive(thread, mAms.mProcessStats);
-        doReturn(r).when(mAms).getProcessRecordLocked(eq(r.info.processName), eq(r.info.uid));
 
         final IIntentReceiver receiver = mock(IIntentReceiver.class);
         final IBinder receiverBinder = new Binder();
@@ -230,15 +382,36 @@
                 UserHandle.getUserId(r.info.uid), receiver);
         mRegisteredReceivers.put(r.getPid(), receiverList);
 
+        doReturn(42L).when(r).getCpuDelayTime();
+
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting killLocked() for "
+                    + Arrays.toString(invocation.getArguments()));
+            mActiveProcesses.remove(r);
+            mRegisteredReceivers.remove(r.getPid());
+            return invocation.callRealMethod();
+        }).when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean());
+
+        // If we're entirely dead, rely on default behaviors above
+        if (dead) return r;
+
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleReceiver() for "
                     + Arrays.toString(invocation.getArguments()));
-            mHandlerThread.getThreadHandler().post(() -> {
-                synchronized (mAms) {
-                    mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                            null, null, false, false);
-                }
-            });
+            final Intent intent = invocation.getArgument(0);
+            final Bundle extras = invocation.getArgument(5);
+            mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
+            if (!wedge) {
+                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+                assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+                        mQueue.getPreferredSchedulingGroupLocked(r));
+                mHandlerThread.getThreadHandler().post(() -> {
+                    synchronized (mAms) {
+                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
+                                extrasOperator.apply(extras), abort, false);
+                    }
+                });
+            }
             return null;
         }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(),
                 anyInt(), anyInt());
@@ -246,12 +419,18 @@
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
                     + Arrays.toString(invocation.getArguments()));
+            final Intent intent = invocation.getArgument(1);
+            final Bundle extras = invocation.getArgument(4);
             final boolean ordered = invocation.getArgument(5);
-            if (ordered) {
+            mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
+            if (!wedge && ordered) {
+                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+                assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+                        mQueue.getPreferredSchedulingGroupLocked(r));
                 mHandlerThread.getThreadHandler().post(() -> {
                     synchronized (mAms) {
                         mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                                null, null, false, false);
+                                null, extrasOperator.apply(extras), abort, false);
                     }
                 });
             }
@@ -262,27 +441,49 @@
         return r;
     }
 
-    private ApplicationInfo makeApplicationInfo(String packageName) {
+    private Pair<Integer, String> makeScheduledBroadcast(ProcessRecord app, Intent intent) {
+        return Pair.create(app.getPid(), intent.getAction());
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName) {
+        return makeApplicationInfo(packageName, packageName, UserHandle.USER_SYSTEM);
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName, String processName, int userId) {
         final ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
-        ai.processName = packageName;
-        ai.uid = getUidForPackage(packageName);
+        ai.processName = processName;
+        ai.uid = getUidForPackage(packageName, userId);
         return ai;
     }
 
-    private ResolveInfo makeManifestReceiver(String packageName, String name) {
+    static ResolveInfo makeManifestReceiver(String packageName, String name) {
+        return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
+    }
+
+    static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) {
+        return makeManifestReceiver(packageName, packageName, name, userId);
+    }
+
+    static ResolveInfo makeManifestReceiver(String packageName, String processName, String name,
+            int userId) {
         final ResolveInfo ri = new ResolveInfo();
         ri.activityInfo = new ActivityInfo();
         ri.activityInfo.packageName = packageName;
-        ri.activityInfo.processName = packageName;
+        ri.activityInfo.processName = processName;
         ri.activityInfo.name = name;
-        ri.activityInfo.applicationInfo = makeApplicationInfo(packageName);
+        ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId);
         return ri;
     }
 
     private BroadcastFilter makeRegisteredReceiver(ProcessRecord app) {
+        return makeRegisteredReceiver(app, 0);
+    }
+
+    private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
         final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid());
         final IntentFilter filter = new IntentFilter();
+        filter.setPriority(priority);
         final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
                 receiverList.app.info.packageName, null, null, null, receiverList.uid,
                 receiverList.userId, false, false, true);
@@ -291,16 +492,69 @@
     }
 
     private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
-            List receivers) {
-        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), receivers);
+            List<Object> receivers) {
+        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+                receivers, false, null, null, UserHandle.USER_SYSTEM);
     }
 
     private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
-            BroadcastOptions options, List receivers) {
+            int userId, List<Object> receivers) {
+        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+                receivers, false, null, null, userId);
+    }
+
+    private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp,
+            List<Object> receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) {
+        return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+                receivers, true, orderedResultTo, orderedExtras, UserHandle.USER_SYSTEM);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
+            BroadcastOptions options, List<Object> receivers) {
+        return makeBroadcastRecord(intent, callerApp, options,
+                receivers, false, null, null, UserHandle.USER_SYSTEM);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
+            BroadcastOptions options, List<Object> receivers, boolean ordered,
+            IIntentReceiver orderedResultTo, Bundle orderedExtras, int userId) {
         return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null,
                 callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
-                AppOpsManager.OP_NONE, options, receivers, null, Activity.RESULT_OK, null, null,
-                false, false, false, UserHandle.USER_SYSTEM, false, null, false, null);
+                AppOpsManager.OP_NONE, options, receivers, orderedResultTo, Activity.RESULT_OK,
+                null, orderedExtras, ordered, false, false, userId, false, null,
+                false, null);
+    }
+
+    private static Map<String, Object> asMap(Bundle bundle) {
+        final Map<String, Object> map = new HashMap<>();
+        if (bundle != null) {
+            for (String key : bundle.keySet()) {
+                map.put(key, bundle.get(key));
+            }
+        }
+        return map;
+    }
+
+    private ArgumentMatcher<Intent> componentEquals(String packageName, String className) {
+        return (test) -> {
+            final ComponentName cn = test.getComponent();
+            return (cn != null)
+                    && Objects.equals(cn.getPackageName(), packageName)
+                    && Objects.equals(cn.getClassName(), className);
+        };
+    }
+
+    private ArgumentMatcher<Intent> filterAndExtrasEquals(Intent intent) {
+        return (test) -> {
+            return intent.filterEquals(test)
+                    && Objects.equals(asMap(intent.getExtras()), asMap(test.getExtras()));
+        };
+    }
+
+    private ArgumentMatcher<Intent> filterEquals(Intent intent) {
+        return (test) -> {
+            return intent.filterEquals(test);
+        };
     }
 
     private ArgumentMatcher<Intent> filterEqualsIgnoringComponent(Intent intent) {
@@ -313,6 +567,17 @@
         };
     }
 
+    private ArgumentMatcher<Bundle> bundleEquals(Bundle bundle) {
+        return (test) -> {
+            // TODO: check values in addition to keys
+            return Objects.equals(test.keySet(), bundle.keySet());
+        };
+    }
+
+    private @NonNull Bundle clone(@Nullable Bundle b) {
+        return (b != null) ? new Bundle(b) : new Bundle();
+    }
+
     private void enqueueBroadcast(BroadcastRecord r) {
         synchronized (mAms) {
             mQueue.enqueueBroadcastLocked(r);
@@ -324,8 +589,27 @@
     }
 
     private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {
-        verify(app.getThread()).scheduleReceiver(
+        verifyScheduleReceiver(times(1), app, intent, UserHandle.USER_SYSTEM);
+    }
+
+    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent)
+            throws Exception {
+        verifyScheduleReceiver(mode, app, intent, UserHandle.USER_SYSTEM);
+    }
+
+    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
+            int userId) throws Exception {
+        verify(app.getThread(), mode).scheduleReceiver(
                 argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(),
+                any(), eq(false), eq(userId), anyInt());
+    }
+
+    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
+            ComponentName component) throws Exception {
+        final Intent targetedIntent = new Intent(intent);
+        targetedIntent.setComponent(component);
+        verify(app.getThread(), mode).scheduleReceiver(
+                argThat(filterEquals(targetedIntent)), any(), any(), anyInt(), any(),
                 any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
     }
 
@@ -336,18 +620,26 @@
                 anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
     }
 
-    private static final String PACKAGE_RED = "com.example.red";
-    private static final String PACKAGE_GREEN = "com.example.green";
-    private static final String PACKAGE_BLUE = "com.example.blue";
-    private static final String PACKAGE_YELLOW = "com.example.yellow";
+    static final int USER_GUEST = 11;
 
-    private static final String CLASS_RED = "com.example.red.Red";
-    private static final String CLASS_GREEN = "com.example.green.Green";
-    private static final String CLASS_BLUE = "com.example.blue.Blue";
-    private static final String CLASS_YELLOW = "com.example.yellow.Yellow";
+    static final String PACKAGE_ANDROID = "android";
+    static final String PACKAGE_PHONE = "com.android.phone";
+    static final String PACKAGE_RED = "com.example.red";
+    static final String PACKAGE_GREEN = "com.example.green";
+    static final String PACKAGE_BLUE = "com.example.blue";
+    static final String PACKAGE_YELLOW = "com.example.yellow";
 
-    private static int getUidForPackage(String packageName) {
+    static final String PROCESS_SYSTEM = "system";
+
+    static final String CLASS_RED = "com.example.red.Red";
+    static final String CLASS_GREEN = "com.example.green.Green";
+    static final String CLASS_BLUE = "com.example.blue.Blue";
+    static final String CLASS_YELLOW = "com.example.yellow.Yellow";
+
+    static int getUidForPackage(@NonNull String packageName) {
         switch (packageName) {
+            case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID;
+            case PACKAGE_PHONE: return android.os.Process.PHONE_UID;
             case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1;
             case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2;
             case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3;
@@ -356,6 +648,39 @@
         }
     }
 
+    static int getUidForPackage(@NonNull String packageName, int userId) {
+        return UserHandle.getUid(userId, getUidForPackage(packageName));
+    }
+
+    /**
+     * Baseline verification of common debugging infrastructure, mostly to make
+     * sure it doesn't crash.
+     */
+    @Test
+    public void testDebugging() throws Exception {
+        // To maximize test coverage, dump current state; we're not worried
+        // about the actual output, just that we don't crash
+        mQueue.dumpDebug(new ProtoOutputStream(),
+                ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+        mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+                null, 0, true, true, true, null, false);
+        mQueue.dumpToDropBoxLocked(TAG);
+
+        BroadcastQueue.logv(TAG);
+        BroadcastQueue.logv(TAG, null);
+        BroadcastQueue.logv(TAG, new PrintWriter(new ByteArrayOutputStream()));
+
+        BroadcastQueue.logw(TAG);
+
+        assertNotNull(mQueue.toString());
+        assertNotNull(mQueue.describeStateLocked());
+
+        for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {
+            assertNotNull(deliveryStateToString(i));
+            assertNotNull(reasonToString(i));
+        }
+    }
+
     /**
      * Verify dispatch of simple broadcast to single manifest receiver in
      * already-running warm app.
@@ -491,7 +816,10 @@
                         makeRegisteredReceiver(receiverYellowApp))));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+        airplane.setComponent(new ComponentName(PACKAGE_YELLOW, CLASS_YELLOW));
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.recordResponseEventWhileInBackground(42L);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options,
                 List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
 
         waitForIdle();
@@ -502,5 +830,658 @@
         verifyScheduleReceiver(receiverBlueApp, timezone);
         verifyScheduleRegisteredReceiver(receiverYellowApp, timezone);
         verifyScheduleReceiver(receiverYellowApp, airplane);
+
+        for (ProcessRecord receiverApp : new ProcessRecord[] {
+                receiverGreenApp, receiverBlueApp, receiverYellowApp
+        }) {
+            // Confirm expected OOM adjustments; we were invoked once to upgrade
+            // and once to downgrade
+            assertEquals(ActivityManager.PROCESS_STATE_RECEIVER,
+                    receiverApp.mState.getReportedProcState());
+            verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
+
+            if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
+                // Nuance: the default implementation doesn't ask for manifest
+                // cold-started apps to be thawed, but the modern stack does
+            } else {
+                // Confirm that app was thawed
+                verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
+                        eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+
+                // Confirm that we added package to process
+                verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+                        anyLong(), any());
+            }
+
+            // Confirm that we've reported package as being used
+            verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
+                    eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
+
+            // Confirm that we unstopped manifest receivers
+            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
+                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+        }
+
+        // Confirm that we've reported expected usage events
+        verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid),
+                eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt());
+        verify(mAms.mUsageStatsService).reportEvent(eq(PACKAGE_YELLOW), eq(UserHandle.USER_SYSTEM),
+                eq(Event.APP_COMPONENT_USED));
+    }
+
+    /**
+     * Verify that we detect and ANR a wedged process.
+     */
+    @Test
+    public void testWedged() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.WEDGE);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+        waitForIdle();
+        verify(mAms).appNotResponding(eq(receiverApp), any());
+    }
+
+    /**
+     * Verify that we handle registered receivers in a process that always
+     * responds with {@link DeadObjectException}, recovering to restart the
+     * process and deliver their next broadcast.
+     */
+    @Test
+    public void testDead_Registered() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.DEAD);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp))));
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+        waitForIdle();
+
+        // First broadcast should have already been dead
+        verifyScheduleRegisteredReceiver(receiverApp, airplane);
+        verify(receiverApp).scheduleCrashLocked(any(),
+                eq(CannotDeliverBroadcastException.TYPE_ID), any());
+
+        // Second broadcast in new process should work fine
+        final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        assertNotEquals(receiverApp, restartedReceiverApp);
+        verifyScheduleReceiver(restartedReceiverApp, timezone);
+    }
+
+    /**
+     * Verify that we handle manifest receivers in a process that always
+     * responds with {@link DeadObjectException}, recovering to restart the
+     * process and deliver their next broadcast.
+     */
+    @Test
+    public void testDead_Manifest() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.DEAD);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+        waitForIdle();
+
+        // First broadcast should have already been dead
+        verifyScheduleReceiver(receiverApp, airplane);
+        verify(receiverApp).scheduleCrashLocked(any(),
+                eq(CannotDeliverBroadcastException.TYPE_ID), any());
+
+        // Second broadcast in new process should work fine
+        final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        assertNotEquals(receiverApp, restartedReceiverApp);
+        verifyScheduleReceiver(restartedReceiverApp, timezone);
+    }
+
+    /**
+     * Verify that we handle the system failing to start a process.
+     */
+    @Test
+    public void testFailStartProcess() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        // Send broadcast while process starts are failing
+        mFailStartProcess = true;
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+
+        // Confirm that queue goes idle, with no processes
+        waitForIdle();
+        assertEquals(1, mActiveProcesses.size());
+
+        // Send more broadcasts with working process starts
+        mFailStartProcess = false;
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+
+        // Confirm that we only saw second broadcast
+        waitForIdle();
+        assertEquals(3, mActiveProcesses.size());
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        verifyScheduleReceiver(never(), receiverGreenApp, airplane);
+        verifyScheduleReceiver(never(), receiverYellowApp, airplane);
+        verifyScheduleReceiver(times(1), receiverGreenApp, timezone);
+        verifyScheduleReceiver(times(1), receiverYellowApp, timezone);
+    }
+
+    /**
+     * Verify that we cleanup a disabled component, skipping a pending dispatch
+     * of broadcast to that component.
+     */
+    @Test
+    public void testCleanup() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        try (SyncBarrier b = new SyncBarrier()) {
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+                    List.of(makeRegisteredReceiver(receiverApp),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
+
+            synchronized (mAms) {
+                mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
+                        UserHandle.USER_SYSTEM);
+
+                // Also try clearing out other unrelated things that should leave
+                // the final receiver intact
+                mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
+                        UserHandle.USER_SYSTEM);
+                mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
+            }
+
+            // To maximize test coverage, dump current state; we're not worried
+            // about the actual output, just that we don't crash
+            mQueue.dumpDebug(new ProtoOutputStream(),
+                    ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+            mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+                    null, 0, true, true, true, null, false);
+        }
+
+        waitForIdle();
+        verifyScheduleRegisteredReceiver(receiverApp, airplane);
+        verifyScheduleReceiver(times(1), receiverApp, airplane,
+                new ComponentName(PACKAGE_GREEN, CLASS_RED));
+        verifyScheduleReceiver(never(), receiverApp, airplane,
+                new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+        verifyScheduleReceiver(times(1), receiverApp, airplane,
+                new ComponentName(PACKAGE_GREEN, CLASS_BLUE));
+    }
+
+    /**
+     * Verify that killing a running process skips registered receivers.
+     */
+    @Test
+    public void testKill() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        try (SyncBarrier b = new SyncBarrier()) {
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+                    List.of(makeRegisteredReceiver(oldApp),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
+
+            synchronized (mAms) {
+                oldApp.killLocked(TAG, 42, false);
+                mQueue.onApplicationCleanupLocked(oldApp);
+            }
+        }
+        waitForIdle();
+
+        // Confirm that we cold-started after the kill
+        final ProcessRecord newApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        assertNotEquals(oldApp, newApp);
+
+        // Confirm that we saw no registered receiver traffic
+        final IApplicationThread oldThread = oldApp.getThread();
+        verify(oldThread, never()).scheduleRegisteredReceiver(any(),
+                any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+        final IApplicationThread newThread = newApp.getThread();
+        verify(newThread, never()).scheduleRegisteredReceiver(any(),
+                any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+
+        // Confirm that we saw final manifest broadcast
+        verifyScheduleReceiver(times(1), newApp, airplane,
+                new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+    }
+
+    /**
+     * Verify that we skip broadcasts to an app being backed up.
+     */
+    @Test
+    public void testBackup() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        receiverApp.setInFullBackup(true);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+        waitForIdle();
+        verifyScheduleReceiver(never(), receiverApp, airplane,
+                new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+    }
+
+    /**
+     * Verify that an ordered broadcast collects results from everyone along the
+     * chain, and is delivered to final destination.
+     */
+    @Test
+    public void testOrdered() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        // Purposefully warm-start the middle apps to make sure we dispatch to
+        // both cold and warm apps in expected order
+        makeActiveProcessRecord(makeApplicationInfo(PACKAGE_BLUE), PACKAGE_BLUE,
+                ProcessBehavior.NORMAL, (extras) -> {
+                    extras = clone(extras);
+                    extras.putBoolean(PACKAGE_BLUE, true);
+                    return extras;
+                });
+        makeActiveProcessRecord(makeApplicationInfo(PACKAGE_YELLOW), PACKAGE_YELLOW,
+                ProcessBehavior.NORMAL, (extras) -> {
+                    extras = clone(extras);
+                    extras.putBoolean(PACKAGE_YELLOW, true);
+                    return extras;
+                });
+
+        final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+        final Bundle orderedExtras = new Bundle();
+        orderedExtras.putBoolean(PACKAGE_RED, true);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)),
+                orderedResultTo, orderedExtras));
+
+        waitForIdle();
+        final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN)).getThread();
+        final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+                getUidForPackage(PACKAGE_BLUE)).getThread();
+        final IApplicationThread yellowThread = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW)).getThread();
+        final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED,
+                getUidForPackage(PACKAGE_RED)).getThread();
+
+        // Verify that we called everyone in specific order, and that each of
+        // them observed the expected extras at that stage
+        final InOrder inOrder = inOrder(greenThread, blueThread, yellowThread, redThread);
+        final Bundle expectedExtras = new Bundle();
+        expectedExtras.putBoolean(PACKAGE_RED, true);
+        inOrder.verify(greenThread).scheduleReceiver(
+                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+                eq(UserHandle.USER_SYSTEM), anyInt());
+        inOrder.verify(blueThread).scheduleReceiver(
+                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+                eq(UserHandle.USER_SYSTEM), anyInt());
+        expectedExtras.putBoolean(PACKAGE_BLUE, true);
+        inOrder.verify(yellowThread).scheduleReceiver(
+                argThat(filterEqualsIgnoringComponent(airplane)), any(), any(),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(true),
+                eq(UserHandle.USER_SYSTEM), anyInt());
+        expectedExtras.putBoolean(PACKAGE_YELLOW, true);
+        inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)), eq(false),
+                anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+        // Finally, verify that we thawed the final receiver
+        verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
+                eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+    }
+
+    /**
+     * Verify that an ordered broadcast can be aborted partially through
+     * dispatch, and is then delivered to final destination.
+     */
+    @Test
+    public void testOrdered_Aborting() throws Exception {
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        doOrdered_Aborting(airplane);
+    }
+
+    /**
+     * Verify that an ordered broadcast marked with
+     * {@link Intent#FLAG_RECEIVER_NO_ABORT} cannot be aborted partially through
+     * dispatch, and is delivered to everyone in order.
+     */
+    @Test
+    public void testOrdered_Aborting_NoAbort() throws Exception {
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        airplane.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+        doOrdered_Aborting(airplane);
+    }
+
+    public void doOrdered_Aborting(@NonNull Intent intent) throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        // Create a process that aborts any ordered broadcasts
+        makeActiveProcessRecord(makeApplicationInfo(PACKAGE_GREEN), PACKAGE_GREEN,
+                ProcessBehavior.ABORT, (extras) -> {
+                    extras = clone(extras);
+                    extras.putBoolean(PACKAGE_GREEN, true);
+                    return extras;
+                });
+        makeActiveProcessRecord(PACKAGE_BLUE);
+
+        final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+
+        enqueueBroadcast(makeOrderedBroadcastRecord(intent, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)),
+                orderedResultTo, null));
+
+        waitForIdle();
+        final IApplicationThread greenThread = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN)).getThread();
+        final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+                getUidForPackage(PACKAGE_BLUE)).getThread();
+        final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED,
+                getUidForPackage(PACKAGE_RED)).getThread();
+
+        final Bundle expectedExtras = new Bundle();
+        expectedExtras.putBoolean(PACKAGE_GREEN, true);
+
+        // Verify that we always invoke the first receiver, but then we might
+        // have invoked or skipped the second receiver depending on the intent
+        // flag policy; we always deliver to final receiver regardless of abort
+        final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
+        inOrder.verify(greenThread).scheduleReceiver(
+                argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
+                eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
+                anyInt());
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
+            inOrder.verify(blueThread).scheduleReceiver(
+                    argThat(filterEqualsIgnoringComponent(intent)), any(), any(),
+                    eq(Activity.RESULT_OK), any(), any(), eq(true), eq(UserHandle.USER_SYSTEM),
+                    anyInt());
+        } else {
+            inOrder.verify(blueThread, never()).scheduleReceiver(any(), any(), any(), anyInt(),
+                    any(), any(), anyBoolean(), anyInt(), anyInt());
+        }
+        inOrder.verify(redThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(intent)),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(expectedExtras)),
+                eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+    }
+
+    /**
+     * Verify that we immediately dispatch final result for empty lists.
+     */
+    @Test
+    public void testOrdered_Empty() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final IApplicationThread callerThread = callerApp.getThread();
+
+        final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+        final Bundle orderedExtras = new Bundle();
+        orderedExtras.putBoolean(PACKAGE_RED, true);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp, null,
+                orderedResultTo, orderedExtras));
+
+        waitForIdle();
+        verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
+                eq(Activity.RESULT_OK), any(), argThat(bundleEquals(orderedExtras)), eq(false),
+                anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+    }
+
+    /**
+     * Verify that we're not surprised by a process attempting to finishing a
+     * broadcast when none is in progress.
+     */
+    @Test
+    public void testUnexpected() throws Exception {
+        final ProcessRecord app = makeActiveProcessRecord(PACKAGE_RED);
+        mQueue.finishReceiverLocked(app, Activity.RESULT_OK, null, null, false, false);
+    }
+
+    @Test
+    public void testBackgroundActivityStarts() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Binder backgroundActivityStartsToken = new Binder();
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastRecord r = new BroadcastRecord(mQueue, intent, callerApp,
+                callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false,
+                null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, Activity.RESULT_OK,
+                null, null, false, false, false, UserHandle.USER_SYSTEM, true,
+                backgroundActivityStartsToken, false, null);
+        enqueueBroadcast(r);
+
+        waitForIdle();
+        verify(receiverApp).addOrUpdateAllowBackgroundActivityStartsToken(eq(r),
+                eq(backgroundActivityStartsToken));
+        verify(receiverApp).removeAllowBackgroundActivityStartsToken(eq(r));
+    }
+
+    @Test
+    public void testOptions_TemporaryAppAllowlist() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setTemporaryAppAllowlist(1_000,
+                PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                PowerExemptionManager.REASON_VPN, TAG);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+        waitForIdle();
+        verify(mAms).tempAllowlistUidLocked(eq(receiverApp.uid), eq(1_000L),
+                eq(options.getTemporaryAppAllowlistReasonCode()), any(),
+                eq(options.getTemporaryAppAllowlistType()), eq(callerApp.uid));
+    }
+
+    /**
+     * Verify that sending broadcasts to the {@code system} process are handled
+     * as a singleton process.
+     */
+    @Test
+    public void testSystemSingleton() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_PHONE);
+        final ProcessRecord systemApp = makeActiveProcessRecord(PACKAGE_ANDROID, PROCESS_SYSTEM,
+                ProcessBehavior.NORMAL, USER_SYSTEM);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_SYSTEM,
+                List.of(makeManifestReceiver(PACKAGE_ANDROID, PROCESS_SYSTEM,
+                        CLASS_GREEN, USER_SYSTEM))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST,
+                List.of(makeManifestReceiver(PACKAGE_ANDROID, PROCESS_SYSTEM,
+                        CLASS_GREEN, USER_GUEST))));
+        waitForIdle();
+
+        // Confirm we dispatched both users to same singleton instance
+        verifyScheduleReceiver(times(1), systemApp, airplane, USER_SYSTEM);
+        verifyScheduleReceiver(times(1), systemApp, airplane, USER_GUEST);
+    }
+
+    /**
+     * Verify that when dispatching we respect tranches of priority.
+     */
+    @Test
+    public void testPriority() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+        final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+        // Enqueue a normal broadcast that will go to several processes, and
+        // then enqueue a foreground broadcast that risks reordering
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        try (SyncBarrier b = new SyncBarrier()) {
+            enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                    List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+                            makeRegisteredReceiver(receiverGreenApp, 10),
+                            makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+                            makeRegisteredReceiver(receiverYellowApp, -10))));
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                    List.of(makeRegisteredReceiver(receiverBlueApp))));
+        }
+
+        waitForIdle();
+
+        // Ignore the final foreground broadcast
+        mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+        assertEquals(5, mScheduledBroadcasts.size());
+
+        // We're only concerned about enforcing ordering between tranches;
+        // within a tranche we're okay with reordering
+        assertEquals(
+                Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+                        makeScheduledBroadcast(receiverGreenApp, timezone)),
+                Set.of(mScheduledBroadcasts.remove(0),
+                        mScheduledBroadcasts.remove(0)));
+        assertEquals(
+                Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+                        makeScheduledBroadcast(receiverYellowApp, timezone)),
+                Set.of(mScheduledBroadcasts.remove(0),
+                        mScheduledBroadcasts.remove(0)));
+        assertEquals(
+                Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+                Set.of(mScheduledBroadcasts.remove(0)));
+    }
+
+    /**
+     * Verify that we handle replacing a pending broadcast.
+     */
+    @Test
+    public void testReplacePending() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final IApplicationThread callerThread = callerApp.getThread();
+
+        final Intent timezoneFirst = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        timezoneFirst.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        timezoneFirst.putExtra(Intent.EXTRA_TIMEZONE, "GMT+5");
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final Intent timezoneSecond = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        timezoneSecond.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        timezoneSecond.putExtra(Intent.EXTRA_TIMEZONE, "GMT-5");
+
+        final IIntentReceiver resultToFirst = mock(IIntentReceiver.class);
+        final IIntentReceiver resultToSecond = mock(IIntentReceiver.class);
+
+        try (SyncBarrier b = new SyncBarrier()) {
+            enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
+                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+                    resultToFirst, null));
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
+            enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
+                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+                    resultToSecond, null));
+        }
+
+        waitForIdle();
+        final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+                getUidForPackage(PACKAGE_BLUE)).getThread();
+        final InOrder inOrder = inOrder(callerThread, blueThread);
+
+        // First broadcast is canceled
+        inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
+                argThat(filterAndExtrasEquals(timezoneFirst)), eq(Activity.RESULT_CANCELED), any(),
+                any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+        // We deliver second broadcast to app
+        timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
+        inOrder.verify(blueThread).scheduleReceiver(
+                argThat(filterAndExtrasEquals(timezoneSecond)),
+                any(), any(), anyInt(), any(), any(), eq(true), anyInt(), anyInt());
+
+        // Second broadcast is finished
+        timezoneSecond.setComponent(null);
+        inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
+                argThat(filterAndExtrasEquals(timezoneSecond)), eq(Activity.RESULT_OK), any(),
+                any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+        // Since we "replaced" the first broadcast in its original position,
+        // only now do we see the airplane broadcast
+        airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
+        inOrder.verify(blueThread).scheduleReceiver(
+                argThat(filterEquals(airplane)),
+                any(), any(), anyInt(), any(), any(), eq(false), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testIdleAndBarrier() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final long beforeFirst;
+        final long afterFirst;
+        final long afterSecond;
+
+        beforeFirst = SystemClock.uptimeMillis() - 10;
+        assertTrue(mQueue.isIdleLocked());
+        assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+
+        try (SyncBarrier b = new SyncBarrier()) {
+            final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+            enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                    List.of(makeRegisteredReceiver(receiverApp))));
+            afterFirst = SystemClock.uptimeMillis();
+
+            assertFalse(mQueue.isIdleLocked());
+            assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+            assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+
+            final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                    List.of(makeRegisteredReceiver(receiverApp))));
+            afterSecond = SystemClock.uptimeMillis() + 10;
+
+            assertFalse(mQueue.isIdleLocked());
+            assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+            assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+            assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
+        }
+
+        mQueue.waitForBarrier(null);
+        assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
+
+        mQueue.waitForIdle(null);
+        assertTrue(mQueue.isIdleLocked());
+        assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+        assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
+        assertTrue(mQueue.isBeyondBarrierLocked(afterSecond));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
rename to services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6be37..161dfa0 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,11 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
+import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
 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 static org.mockito.ArgumentMatchers.eq;
@@ -37,21 +39,26 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
+
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.BiFunction;
 
 /**
  * Test class for {@link BroadcastRecord}.
@@ -60,7 +67,7 @@
  *  atest FrameworksServicesTests:BroadcastRecordTest
  */
 @SmallTest
-@Presubmit
+@RunWith(MockitoJUnitRunner.class)
 public class BroadcastRecordTest {
 
     private static final int USER0 = UserHandle.USER_SYSTEM;
@@ -73,8 +80,9 @@
     private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3,
             PACKAGE4};
 
-    @Mock
-    ActivityManagerInternal mActivityManagerInternal;
+    @Mock ActivityManagerInternal mActivityManagerInternal;
+    @Mock BroadcastQueue mQueue;
+    @Mock ProcessRecord mProcess;
 
     @Before
     public void setUp() throws Exception {
@@ -82,6 +90,91 @@
     }
 
     @Test
+    public void testIsPrioritized_Empty() {
+        assertFalse(isPrioritized(List.of()));
+    }
+
+    @Test
+    public void testIsPrioritized_Single() {
+        assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
+        assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
+        assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+    }
+
+    @Test
+    public void testIsPrioritized_No() {
+        assertFalse(isPrioritized(List.of(
+                createResolveInfo(PACKAGE1, getAppId(1), 0),
+                createResolveInfo(PACKAGE2, getAppId(2), 0),
+                createResolveInfo(PACKAGE3, getAppId(3), 0))));
+        assertFalse(isPrioritized(List.of(
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
+                createResolveInfo(PACKAGE2, getAppId(2), 10),
+                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+    }
+
+    @Test
+    public void testIsPrioritized_Yes() {
+        assertTrue(isPrioritized(List.of(
+                createResolveInfo(PACKAGE1, getAppId(1), -10),
+                createResolveInfo(PACKAGE2, getAppId(2), 0),
+                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+        assertTrue(isPrioritized(List.of(
+                createResolveInfo(PACKAGE1, getAppId(1), 0),
+                createResolveInfo(PACKAGE2, getAppId(2), 0),
+                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+    }
+
+    @Test
+    public void testGetReceiverIntent_Simple() {
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final BroadcastRecord r = createBroadcastRecord(
+                List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent);
+        final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+        assertEquals(PACKAGE1, actual.getComponent().getPackageName());
+        assertNull(r.intent.getComponent());
+    }
+
+    @Test
+    public void testGetReceiverIntent_Filtered_Partial() {
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra(Intent.EXTRA_INDEX, 42);
+        final BroadcastRecord r = createBroadcastRecord(
+                List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
+                (uid, extras) -> {
+                    return Bundle.EMPTY;
+                });
+        final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+        assertEquals(PACKAGE1, actual.getComponent().getPackageName());
+        assertEquals(-1, actual.getIntExtra(Intent.EXTRA_INDEX, -1));
+        assertNull(r.intent.getComponent());
+        assertEquals(42, r.intent.getIntExtra(Intent.EXTRA_INDEX, -1));
+    }
+
+    @Test
+    public void testGetReceiverIntent_Filtered_Complete() {
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra(Intent.EXTRA_INDEX, 42);
+        final BroadcastRecord r = createBroadcastRecord(
+                List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
+                (uid, extras) -> {
+                    return null;
+                });
+        final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+        assertNull(actual);
+        assertNull(r.intent.getComponent());
+        assertEquals(42, r.intent.getIntExtra(Intent.EXTRA_INDEX, -1));
+    }
+
+    @Test
+    public void testIsReceiverEquals() {
+        final ResolveInfo info = createResolveInfo(PACKAGE1, getAppId(1));
+        assertTrue(isReceiverEquals(info, info));
+        assertTrue(isReceiverEquals(info, createResolveInfo(PACKAGE1, getAppId(1))));
+        assertFalse(isReceiverEquals(info, createResolveInfo(PACKAGE2, getAppId(2))));
+    }
+
+    @Test
     public void testCleanupDisabledPackageReceivers() {
         final int user0 = UserHandle.USER_SYSTEM;
         final int user1 = user0 + 1;
@@ -360,13 +453,20 @@
     }
 
     private static ResolveInfo createResolveInfo(String packageName, int uid) {
+        return createResolveInfo(packageName, uid, 0);
+    }
+
+    private static ResolveInfo createResolveInfo(String packageName, int uid, int priority) {
         final ResolveInfo resolveInfo = new ResolveInfo();
         final ActivityInfo activityInfo = new ActivityInfo();
         final ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = packageName;
         appInfo.uid = uid;
         activityInfo.applicationInfo = appInfo;
+        activityInfo.packageName = packageName;
+        activityInfo.name = packageName + ".MyReceiver";
         resolveInfo.activityInfo = activityInfo;
+        resolveInfo.priority = priority;
         return resolveInfo;
     }
 
@@ -402,13 +502,18 @@
         return excludedList;
     }
 
-    private static BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
+        return createBroadcastRecord(receivers, userId, intent, null);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         return new BroadcastRecord(
-                null /* queue */,
+                mQueue /* queue */,
                 intent,
-                null /* callerApp */,
-                null  /* callerPackage */,
+                mProcess /* callerApp */,
+                PACKAGE1 /* callerPackage */,
                 null /* callerFeatureId */,
                 0 /* callingPid */,
                 0 /* callingUid */,
@@ -431,7 +536,7 @@
                 false /* allowBackgroundActivityStarts */,
                 null /* activityStartsToken */,
                 false /* timeoutExempt */,
-                null /* filterExtrasForReceiver */);
+                filterExtrasForReceiver);
     }
 
     private static int getAppId(int i) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
new file mode 100644
index 0000000..5dc1251
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+public class AppOpsLegacyRestrictionsTest {
+    private static final int UID_ANY = -2;
+
+    final Object mClientToken = new Object();
+    final int mUserId1 = 65001;
+    final int mUserId2 = 65002;
+    final int mOpCode1 = OP_COARSE_LOCATION;
+    final int mOpCode2 = OP_FINE_LOCATION;
+    final String mPackageName = "com.example.test";
+    final String mAttributionTag = "test-attribution-tag";
+
+    StaticMockitoSession mSession;
+
+    @Mock
+    AppOpsService.Constants mConstants;
+
+    @Mock
+    Context mContext;
+
+    @Mock
+    Handler mHandler;
+
+    @Mock
+    AppOpsServiceInterface mLegacyAppOpsService;
+
+    AppOpsRestrictions mAppOpsRestrictions;
+
+    @Before
+    public void setUp() {
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
+        mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
+        mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
+        Mockito.when(mHandler.post(Mockito.any(Runnable.class))).then(inv -> {
+            Runnable r = inv.getArgument(0);
+            r.run();
+            return true;
+        });
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService);
+    }
+
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
+    }
+
+    @Test
+    public void testSetAndGetSingleGlobalRestriction() {
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+        // Act: add a restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+        // Act: add same restriction again (expect false; should be no-op)
+        assertEquals(false, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        assertEquals(true, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+        // Act: remove the restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+        // Act: remove same restriction again (expect false; should be no-op)
+        assertEquals(false,
+                mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+    }
+
+    @Test
+    public void testSetAndGetDoubleGlobalRestriction() {
+        // Act: add opCode1 restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+        // Act: add opCode2 restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        // Act: remove opCode1 restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        // Act: remove opCode2 restriction
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, false));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+    }
+
+    @Test
+    public void testClearGlobalRestrictions() {
+        // Act: clear (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+        // Act: add opCodes
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+        assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        // Act: clear
+        assertEquals(true, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+        // Act: clear (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+    }
+
+    @Test
+    public void testSetAndGetSingleUserRestriction() {
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+        // Act: add a restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, true, null));
+        // Act: add the restriction again (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, true, null));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+        // Act: remove the restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, false, null));
+        // Act: remove the restriction again (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, false, null));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+    }
+
+    @Test
+    public void testSetAndGetDoubleUserRestriction() {
+        // Act: add opCode1 restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, true, null));
+        // Act: add opCode2 restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode2, true, null));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+        // Act: remove opCode1 restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, false, null));
+        // Verify: opCode1 is removed but not opCode22
+        assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+        // Act: remove opCode2 restriction
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode2, false, null));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+        assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+    }
+
+    @Test
+    public void testClearUserRestrictionsAllUsers() {
+        // Act: clear (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+        // Act: add restrictions
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode2, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId2, mOpCode1, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId2, mOpCode2, true, null));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        // Act: clear all user restrictions
+        assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+    }
+
+    @Test
+    public void testClearUserRestrictionsSpecificUsers() {
+        // Act: clear (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+        // Act: add restrictions
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode1, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId1, mOpCode2, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId2, mOpCode1, true, null));
+        assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+                mClientToken, mUserId2, mOpCode2, true, null));
+        // Verify: not empty
+        assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+        // Act: clear userId1
+        assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+        // Act: clear userId1 again (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+        // Verify:  userId1 is removed but not userId2
+        assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+        assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+                mClientToken, mUserId2, mOpCode2, mPackageName, mAttributionTag, false));
+        // Act: clear userId2
+        assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+        // Act: clear userId2 again (should be no-op)
+        assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+        // Verify: empty
+        assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+    }
+
+    @Test
+    public void testNotify() {
+        mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null);
+        mAppOpsRestrictions.clearUserRestrictions(mClientToken);
+        Mockito.verify(mLegacyAppOpsService, Mockito.times(1))
+                .notifyWatchersOfChange(mOpCode1, UID_ANY);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 86e12647..e1713b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -34,12 +34,9 @@
 import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -49,25 +46,22 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.Message;
 import android.util.SparseArray;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.Clock;
 import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback;
+import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.quality.Strictness;
 
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.PriorityQueue;
 
 public class AppOpsUidStateTrackerTest {
 
@@ -81,12 +75,11 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    Handler mHandler;
-
-    @Mock
     AppOpsService.Constants mConstants;
 
-    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock();
+    AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
+
+    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor);
 
     AppOpsUidStateTracker mIntf;
 
@@ -101,7 +94,8 @@
         mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
         mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
         mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
-        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants);
+        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants,
+                Thread.currentThread());
     }
 
     @After
@@ -263,18 +257,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -291,18 +277,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -319,14 +297,8 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-
-        getPostDelayedMessageArguments(messageAtomicReference, null);
-        Message message = messageAtomicReference.get();
-
         // 1 ms short of settle time
         mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
-        message.getCallback().run();
         assertForeground(UID);
     }
 
@@ -471,8 +443,6 @@
                 .topState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true));
     }
 
@@ -484,8 +454,6 @@
                 .foregroundServiceState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true));
     }
 
@@ -497,8 +465,6 @@
                 .foregroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true));
     }
 
@@ -510,8 +476,6 @@
                 .backgroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false));
     }
 
@@ -679,7 +643,6 @@
                 .nonExistentState()
                 .update();
 
-        verify(mHandler, never()).post(any());
         verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
     }
 
@@ -695,7 +658,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false));
     }
 
@@ -711,7 +673,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -727,7 +688,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -743,10 +703,32 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
+    @Test
+    public void testUidStateChangedBackgroundThenForegroundImmediately() {
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        UidStateChangedCallback cb = addUidStateChangeCallback();
+
+        procStateBuilder(UID)
+            .backgroundState()
+            .update();
+
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        mClock.advanceTime(1);
+
+        verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+    }
+
     public void testUidStateChangedCallback(int initialState, int finalState) {
         int initialUidState = processStateToUidState(initialState);
         int finalUidState = processStateToUidState(finalState);
@@ -767,13 +749,9 @@
                 .update();
 
         if (finalUidStateIsBackgroundAndLessImportant) {
-            AtomicReference<Message> delayedMessage = new AtomicReference<>();
-            getPostDelayedMessageArguments(delayedMessage, new AtomicLong());
             mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-            delayedMessage.get().getCallback().run();
         }
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce())
                 .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange));
     }
@@ -781,7 +759,7 @@
     private UidStateChangedCallback addUidStateChangeCallback() {
         UidStateChangedCallback cb =
                 Mockito.mock(UidStateChangedCallback.class);
-        mIntf.addUidStateChangedCallback(mHandler, cb);
+        mIntf.addUidStateChangedCallback(r -> r.run(), cb);
         return cb;
     }
 
@@ -795,30 +773,6 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND));
     }
 
-    private void getPostDelayedMessageArguments(AtomicReference<Message> message,
-            AtomicLong delay) {
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-
-        verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture());
-
-        if (message != null) {
-            message.set(messageCaptor.getValue());
-        }
-        if (delay != null) {
-            delay.set(delayCaptor.getValue());
-        }
-    }
-
-    private Message getLatestPostMessageArgument() {
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture());
-
-        return messageCaptor.getValue();
-    }
-
     private UidProcStateUpdateBuilder procStateBuilder(int uid) {
         return new UidProcStateUpdateBuilder(mIntf, uid);
     }
@@ -896,8 +850,14 @@
 
     private static class AppOpsUidStateTrackerTestClock extends Clock {
 
+        private AppOpsUidStateTrackerTestExecutor mExecutor;
         long mElapsedRealTime = 0x5f3759df;
 
+        AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) {
+            mExecutor = executor;
+            executor.setUptime(mElapsedRealTime);
+        }
+
         @Override
         public long elapsedRealtime() {
             return mElapsedRealTime;
@@ -905,6 +865,53 @@
 
         void advanceTime(long time) {
             mElapsedRealTime += time;
+            mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime
+        }
+    }
+
+    private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor {
+
+        private static class QueueElement implements Comparable<QueueElement> {
+
+            private long mExecutionTime;
+            private Runnable mRunnable;
+
+            private QueueElement(long executionTime, Runnable runnable) {
+                mExecutionTime = executionTime;
+                mRunnable = runnable;
+            }
+
+            @Override
+            public int compareTo(QueueElement queueElement) {
+                return Long.compare(mExecutionTime, queueElement.mExecutionTime);
+            }
+        }
+
+        private long mUptime = 0;
+
+        private PriorityQueue<QueueElement> mDelayedMessages = new PriorityQueue();
+
+        @Override
+        public void execute(Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
+        public void executeDelayed(Runnable runnable, long delay) {
+            if (delay <= 0) {
+                execute(runnable);
+            }
+
+            mDelayedMessages.add(new QueueElement(mUptime + delay, runnable));
+        }
+
+        private void setUptime(long uptime) {
+            while (!mDelayedMessages.isEmpty()
+                    && mDelayedMessages.peek().mExecutionTime <= uptime) {
+                mDelayedMessages.poll().mRunnable.run();
+            }
+
+            mUptime = uptime;
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
index c4c3abc..145e66c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
@@ -30,10 +30,13 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraInjectionSession;
 import android.hardware.camera2.CameraManager;
 import android.os.Process;
+import android.os.UserManager;
 import android.testing.TestableContext;
 import android.util.ArraySet;
 
@@ -51,12 +54,17 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 public class CameraAccessControllerTest {
     private static final String FRONT_CAMERA = "0";
     private static final String REAR_CAMERA = "1";
     private static final String TEST_APP_PACKAGE = "some.package";
     private static final String OTHER_APP_PACKAGE = "other.package";
+    private static final int PERSONAL_PROFILE_USER_ID = 0;
+    private static final int WORK_PROFILE_USER_ID = 10;
 
     private CameraAccessController mController;
 
@@ -69,6 +77,8 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private VirtualDeviceManagerInternal mDeviceManagerInternal;
     @Mock
     private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback;
@@ -76,6 +86,7 @@
     private ApplicationInfo mTestAppInfo = new ApplicationInfo();
     private ApplicationInfo mOtherAppInfo = new ApplicationInfo();
     private ArraySet<Integer> mRunningUids = new ArraySet<>();
+    private List<UserInfo> mAliveUsers = new ArrayList<>();
 
     @Captor
     ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor;
@@ -84,6 +95,7 @@
     public void setUp() throws PackageManager.NameNotFoundException {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(CameraManager.class, mCameraManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
         mContext.setMockPackageManager(mPackageManager);
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
         LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal);
@@ -92,10 +104,14 @@
         mTestAppInfo.uid = Process.FIRST_APPLICATION_UID;
         mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1;
         mRunningUids.add(Process.FIRST_APPLICATION_UID);
-        when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn(
-                mTestAppInfo);
-        when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn(
-                mOtherAppInfo);
+        mAliveUsers.add(new UserInfo(PERSONAL_PROFILE_USER_ID, "", 0));
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                anyInt())).thenReturn(mTestAppInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(OTHER_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                anyInt())).thenReturn(mOtherAppInfo);
+        when(mUserManager.getAliveUsers()).thenReturn(mAliveUsers);
         mController.startObservingIfNeeded();
     }
 
@@ -227,4 +243,74 @@
 
         verify(mCameraManager, times(1)).injectCamera(any(), any(), any(), any(), any());
     }
+
+    @Test
+    public void multipleUsers_getPersonalProfileAppUid_cameraBlocked()
+            throws CameraAccessException, NameNotFoundException {
+        mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0));
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(PERSONAL_PROFILE_USER_ID))).thenReturn(mTestAppInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(WORK_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class);
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void multipleUsers_getPersonalProfileAppUid_noCameraBlocking()
+            throws CameraAccessException, NameNotFoundException {
+        mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0));
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(PERSONAL_PROFILE_USER_ID))).thenReturn(mTestAppInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(WORK_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class);
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(false);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+
+        verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void multipleUsers_getWorkProfileAppUid_cameraBlocked()
+            throws CameraAccessException, NameNotFoundException {
+        mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0));
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(PERSONAL_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(WORK_PROFILE_USER_ID))).thenReturn(mTestAppInfo);
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(true);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void multipleUsers_getWorkProfileAppUid_noCameraBlocking()
+            throws CameraAccessException, NameNotFoundException {
+        mAliveUsers.add(new UserInfo(WORK_PROFILE_USER_ID, "", 0));
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(PERSONAL_PROFILE_USER_ID))).thenThrow(NameNotFoundException.class);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(TEST_APP_PACKAGE), eq(PackageManager.GET_ACTIVITIES),
+                eq(WORK_PROFILE_USER_ID))).thenReturn(mTestAppInfo);
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(false);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+
+        verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..2df6823a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+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.PowerManager;
+import android.os.test.TestLooper;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.policy.WindowManagerPolicy;
+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.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+    private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
+    private static final int DISPLAY_ID = 42;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerController2.Injector mInjector;
+    private Context mContextSpy;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private LogicalDisplay mLogicalDisplayMock;
+    @Mock
+    private DisplayDevice mDisplayDeviceMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private BrightnessSetting mBrightnessSettingMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private Resources mResourcesMock;
+    @Mock
+    private DisplayDeviceConfig mDisplayDeviceConfigMock;
+    @Mock
+    private DisplayPowerState mDisplayPowerStateMock;
+    @Mock
+    private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+    @Mock
+    private WakelockController mWakelockController;
+
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+        mInjector = new DisplayPowerController2.Injector() {
+            @Override
+            DisplayPowerController2.Clock getClock() {
+                return mClock::now;
+            }
+
+            @Override
+            DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                    int displayId, int displayState) {
+                return mDisplayPowerStateMock;
+            }
+
+            @Override
+            DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                    FloatProperty<DisplayPowerState> firstProperty,
+                    FloatProperty<DisplayPowerState> secondProperty) {
+                return mDualRampAnimatorMock;
+            }
+
+            @Override
+            WakelockController getWakelockController(int displayId,
+                    DisplayPowerCallbacks displayPowerCallbacks) {
+                return mWakelockController;
+            }
+        };
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+
+        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+
+        Sensor proxSensor = setUpProxSensor();
+
+        DisplayPowerController2 dpc = new DisplayPowerController2(
+                mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+                mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+        });
+
+        when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(proxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
+        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+
+
+        dpc.stop();
+        advanceTime(1);
+        // two times, one for unfinished business and one for proximity
+        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
+        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+    }
+
+    /**
+     * 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 Sensor setUpProxSensor() throws Exception {
+        Sensor proxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(proxSensor));
+        return proxSensor;
+    }
+
+    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) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+
+        when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
+        when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
+        when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
+        when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
new file mode 100644
index 0000000..288408c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class WakelockControllerTest {
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
+
+    private WakelockController mWakelockController;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mWakelockController = new WakelockController(DISPLAY_ID, mDisplayPowerCallbacks);
+    }
+
+    @Test
+    public void validateSuspendBlockerIdsAreExpected() {
+        assertEquals(mWakelockController.getSuspendBlockerUnfinishedBusinessId(),
+                "[" + DISPLAY_ID + "]unfinished business");
+        assertEquals(mWakelockController.getSuspendBlockerOnStateChangedId(),
+                "[" + DISPLAY_ID + "]on state changed");
+        assertEquals(mWakelockController.getSuspendBlockerProxPositiveId(),
+                "[" + DISPLAY_ID + "]prox positive");
+        assertEquals(mWakelockController.getSuspendBlockerProxNegativeId(),
+                "[" + DISPLAY_ID + "]prox negative");
+        assertEquals(mWakelockController.getSuspendBlockerProxDebounceId(),
+                "[" + DISPLAY_ID + "]prox debounce");
+    }
+
+    @Test
+    public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() {
+        // Acquire the suspend blocker
+        assertTrue(mWakelockController.acquireStateChangedSuspendBlocker());
+        assertTrue(mWakelockController.isOnStateChangedPending());
+
+        // Try to reacquire
+        assertFalse(mWakelockController.acquireStateChangedSuspendBlocker());
+        assertTrue(mWakelockController.isOnStateChangedPending());
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .acquireSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
+
+        // Release
+        mWakelockController.releaseStateChangedSuspendBlocker();
+        assertFalse(mWakelockController.isOnStateChangedPending());
+
+        // Try to release again
+        mWakelockController.releaseStateChangedSuspendBlocker();
+
+        // Verify release happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .releaseSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
+    }
+
+    @Test
+    public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() {
+        // Acquire the suspend blocker
+        mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
+        assertTrue(mWakelockController.hasUnfinishedBusiness());
+
+        // Try to reacquire
+        mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
+        assertTrue(mWakelockController.hasUnfinishedBusiness());
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .acquireSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
+
+        // Release the suspend blocker
+        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+        assertFalse(mWakelockController.hasUnfinishedBusiness());
+
+        // Try to release again
+        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+
+        // Verify release happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .releaseSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
+    }
+
+    @Test
+    public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() {
+        // Acquire the suspend blocker
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
+
+        // Try to reacquire
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 2);
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(2))
+                .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
+
+        // Release the suspend blocker
+        mWakelockController.releaseProxPositiveSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 0);
+
+        // Verify all suspend blockers were released
+        verify(mDisplayPowerCallbacks, times(2))
+                .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
+    }
+
+    @Test
+    public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() {
+        // Acquire the suspend blocker
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
+
+        // Try to reacquire
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 2);
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(2))
+                .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
+
+        // Release the suspend blocker
+        mWakelockController.releaseProxNegativeSuspendBlocker();
+        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 0);
+
+        // Verify all suspend blockers were released
+        verify(mDisplayPowerCallbacks, times(2))
+                .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
+    }
+
+    @Test
+    public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() {
+        // Acquire the suspend blocker
+        mWakelockController.acquireProxDebounceSuspendBlocker();
+
+        // Try to reacquire
+        mWakelockController.acquireProxDebounceSuspendBlocker();
+        assertTrue(mWakelockController.hasProximitySensorDebounced());
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
+
+        // Release the suspend blocker
+        assertTrue(mWakelockController.releaseProxDebounceSuspendBlocker());
+
+        // Release again
+        assertFalse(mWakelockController.releaseProxDebounceSuspendBlocker());
+        assertFalse(mWakelockController.hasProximitySensorDebounced());
+
+        // Verify suspend blocker was released only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
+    }
+
+    @Test
+    public void proximityPositiveRunnableWorksAsExpected() {
+        // Acquire the suspend blocker twice
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+
+        // Execute the runnable
+        Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
+        proximityPositiveRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
+        verify(mDisplayPowerCallbacks).onProximityPositive();
+        verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+                mWakelockController.getSuspendBlockerProxPositiveId());
+    }
+
+    @Test
+    public void proximityNegativeRunnableWorksAsExpected() {
+        // Acquire the suspend blocker twice
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+
+        // Execute the runnable
+        Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
+        proximityNegativeRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
+        verify(mDisplayPowerCallbacks).onProximityNegative();
+        verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+                mWakelockController.getSuspendBlockerProxNegativeId());
+    }
+
+    @Test
+    public void onStateChangeRunnableWorksAsExpected() {
+        // Acquire the suspend blocker twice
+        mWakelockController.acquireStateChangedSuspendBlocker();
+
+        // Execute the runnable
+        Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
+        stateChangeRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertEquals(mWakelockController.isOnStateChangedPending(), false);
+        verify(mDisplayPowerCallbacks).onStateChanged();
+        verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+                mWakelockController.getSuspendBlockerOnStateChangedId());
+    }
+
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
new file mode 100644
index 0000000..c2768d51
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+/** Version of PackageResetHelper for testing. */
+public class FakePackageResetHelper extends PackageResetHelper {
+
+    public FakePackageResetHelper() {}
+
+    @Override
+    protected void onRegister() {}
+
+    @Override
+    protected void onUnregister() {}
+
+    public boolean isResetableForPackage(String packageName) {
+        return queryResetableForPackage(packageName);
+    }
+
+    public void reset(String packageName) {
+        notifyPackageReset(packageName);
+    }
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 02cacb7..ca73091 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -35,6 +35,7 @@
     private final FakeDeviceIdleHelper mDeviceIdleHelper;
     private final FakeEmergencyHelper mEmergencyHelper;
     private final LocationUsageLogger mLocationUsageLogger;
+    private final FakePackageResetHelper mPackageResetHelper;
 
     public TestInjector(Context context) {
         mUserInfoHelper = new FakeUserInfoHelper();
@@ -50,6 +51,7 @@
         mDeviceIdleHelper = new FakeDeviceIdleHelper();
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
+        mPackageResetHelper = new FakePackageResetHelper();
     }
 
     @Override
@@ -116,4 +118,9 @@
     public LocationUsageLogger getLocationUsageLogger() {
         return mLocationUsageLogger;
     }
+
+    @Override
+    public FakePackageResetHelper getPackageResetHelper() {
+        return mPackageResetHelper;
+    }
 }
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 0ac1443..20e4e80 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
@@ -1218,6 +1218,44 @@
         assertThat(mProvider.getRequest().isActive()).isFalse();
     }
 
+    @Test
+    public void testQueryPackageReset() {
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+
+        ILocationListener listener1 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        ILocationListener listener2 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mManager.unregisterLocationRequest(listener1);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mManager.unregisterLocationRequest(listener2);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+    }
+
+    @Test
+    public void testPackageReset() {
+        ILocationListener listener1 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+        ILocationListener listener2 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+
+        assertThat(mProvider.getRequest().isActive()).isTrue();
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mInjector.getPackageResetHelper().reset("mypackage");
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+    }
+
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index bb5b1d8..cc57b9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -62,10 +62,10 @@
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.dex.DexManager
 import com.android.server.pm.parsing.PackageParser2
-import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.parsing.ParsingPackage
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
@@ -77,14 +77,6 @@
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
 import com.android.server.utils.WatchedArrayMap
-import libcore.util.HexEncoding
-import org.junit.Assert
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import org.mockito.AdditionalMatchers.or
-import org.mockito.Mockito
-import org.mockito.quality.Strictness
 import java.io.File
 import java.io.IOException
 import java.nio.file.Files
@@ -93,6 +85,14 @@
 import java.util.Arrays
 import java.util.Random
 import java.util.concurrent.FutureTask
+import libcore.util.HexEncoding
+import org.junit.Assert
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.AdditionalMatchers.or
+import org.mockito.Mockito
+import org.mockito.quality.Strictness
 
 /**
  * A utility for mocking behavior of the system and dependencies when testing PackageManagerService
@@ -522,7 +522,7 @@
         whenever(mocks.packageParser.parsePackage(
                 or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
         whenever(mocks.packageParser.parsePackage(
-                or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
+                or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
         return parsedPackage
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index 987192d..da929af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -23,6 +23,7 @@
 import android.util.Log
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.testutils.whenever
+import java.io.File
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.notNullValue
@@ -33,13 +34,11 @@
 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.ArgumentMatchers.argThat
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
-import java.io.File
 
 @RunWith(JUnit4::class)
 class PackageManagerServiceBootTest {
@@ -120,8 +119,7 @@
         whenever(rule.mocks().packageParser.parsePackage(
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
-                anyBoolean(),
-                any()))
+                anyBoolean()))
                 .thenThrow(PackageManagerException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 8744f32..e28d331 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -386,7 +386,7 @@
             pkg.setTargetSdkVersion(Build.VERSION_CODES.S)
             libraries?.forEach { pkg.addLibraryName(it) }
             staticLibrary?.let {
-                pkg.setStaticSharedLibName(it)
+                pkg.setStaticSharedLibraryName(it)
                 pkg.setStaticSharedLibVersion(staticLibraryVersion)
                 pkg.setStaticSharedLibrary(true)
             }
@@ -430,7 +430,7 @@
             setTargetSdkVersion(Build.VERSION_CODES.S)
             libraries?.forEach { addLibraryName(it) }
             staticLibrary?.let {
-                setStaticSharedLibName(it)
+                setStaticSharedLibraryName(it)
                 setStaticSharedLibVersion(staticLibraryVersion)
                 setStaticSharedLibrary(true)
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
index 245b4dc..278e04a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
@@ -19,8 +19,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.server.pm.UserManagerInternal.PARENT_DISPLAY;
-
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
@@ -185,10 +183,11 @@
         addDefaultProfileAndParent();
 
         mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        mUmi.assignUserToDisplay(PROFILE_USER_ID, PARENT_DISPLAY);
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
 
-        assertUsersAssignedToDisplays(PARENT_USER_ID, SECONDARY_DISPLAY_ID,
-                pair(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+        Log.v(TAG, "Exception: " + e);
+        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
     }
 
     @Test
@@ -198,7 +197,20 @@
 
         mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
+        enableUsersOnSecondaryDisplays();
+        addDefaultProfileAndParent();
+
+        mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
 
         Log.v(TAG, "Exception: " + e);
         assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
index 6f0efb0..90a5fa0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
@@ -33,7 +33,6 @@
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 import android.util.Log;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -614,24 +613,6 @@
                 .containsExactly(userId, displayId);
     }
 
-    @SafeVarargs
-    protected final void assertUsersAssignedToDisplays(@UserIdInt int userId, int displayId,
-            @SuppressWarnings("unchecked") Pair<Integer, Integer>... others) {
-        Object[] otherObjects = new Object[others.length * 2];
-        for (int i = 0; i < others.length; i++) {
-            Pair<Integer, Integer> other = others[i];
-            otherObjects[i * 2] = other.first;
-            otherObjects[i * 2 + 1] = other.second;
-
-        }
-        assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
-                .containsExactly(userId, displayId, otherObjects);
-    }
-
-    protected static Pair<Integer, Integer> pair(@UserIdInt int userId, int secondaryDisplayId) {
-        return new Pair<>(userId, secondaryDisplayId);
-    }
-
     ///////////////////
     // Private infra //
     ///////////////////
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 47155a1..6da4ab7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -25,6 +25,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 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.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -78,10 +82,13 @@
         }
 
         @Override
-        boolean isPolicyEnabled(int policy) {
+        boolean isPolicyEnabled(int policy, @Nullable DeviceConfig.Properties properties) {
             // Use a limited set of policies so that the test doesn't need to be updated whenever
             // a policy is added or removed.
-            return policy == EconomicPolicy.POLICY_AM || policy == EconomicPolicy.POLICY_JS;
+            if (policy == EconomicPolicy.POLICY_ALARM || policy == EconomicPolicy.POLICY_JOB) {
+                return super.isPolicyEnabled(policy, properties);
+            }
+            return false;
         }
     }
 
@@ -116,10 +123,14 @@
                         -> mDeviceConfigPropertiesBuilder.build())
                 .when(() -> DeviceConfig.getProperties(
                         eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>any()));
+        mDeviceConfigPropertiesBuilder
+                .setBoolean(EconomyManager.KEY_ENABLE_POLICY_ALARM, true)
+                .setBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, true);
 
         // Initialize real objects.
         // Capture the listeners.
         mEconomicPolicy = new CompleteEconomicPolicy(mIrs, mInjector);
+        mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
     }
 
     @After
@@ -129,6 +140,11 @@
         }
     }
 
+    private void setDeviceConfigBoolean(String key, boolean val) {
+        mDeviceConfigPropertiesBuilder.setBoolean(key, val);
+        mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
+    }
+
     private void setDeviceConfigCakes(String key, long valCakes) {
         mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c");
         mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
@@ -182,4 +198,52 @@
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
         assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
     }
+
+
+    @Test
+    public void testPolicyToggling() {
+        setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_ALARM, true);
+        setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, false);
+        assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
+        final String pkgExempted = "com.pkg.exempted";
+        when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+        assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        assertNotNull(mEconomicPolicy.getAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK));
+        assertNull(mEconomicPolicy.getAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START));
+        assertEquals(EconomicPolicy.POLICY_ALARM, mEconomicPolicy.getEnabledPolicyIds());
+        assertTrue(mEconomicPolicy.isPolicyEnabled(EconomicPolicy.POLICY_ALARM));
+        assertFalse(mEconomicPolicy.isPolicyEnabled(EconomicPolicy.POLICY_JOB));
+
+        setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_ALARM, false);
+        setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, true);
+        assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
+        when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+        assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        assertNull(mEconomicPolicy.getAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK));
+        assertNotNull(mEconomicPolicy.getAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START));
+        assertEquals(EconomicPolicy.POLICY_JOB, mEconomicPolicy.getEnabledPolicyIds());
+        assertFalse(mEconomicPolicy.isPolicyEnabled(EconomicPolicy.POLICY_ALARM));
+        assertTrue(mEconomicPolicy.isPolicyEnabled(EconomicPolicy.POLICY_JOB));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index 19b798d..b7bbcd75 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,15 +134,36 @@
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getHardSatiatedConsumptionLimit());
+
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMinSatiatedBalance(0, pkgRestricted));
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
-        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
-                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
+
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
+
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+        assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES
+                        + EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, pkgUpdater));
+        // Make sure it doesn't suggest a min balance greater than max.
+        final int updateCount = (int) (EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+                / EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater)))
+                .thenReturn(updateCount);
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
                 mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
     }
@@ -152,8 +173,10 @@
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
         setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                arcToCake(1));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -163,8 +186,12 @@
         assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
-        assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
-        assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3);
+        assertEquals(arcToCake(4) + 3 * arcToCake(1),
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
     }
 
     @Test
@@ -175,6 +202,8 @@
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                arcToCake(-4));
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -186,6 +215,10 @@
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+        assertEquals(arcToCake(0) + 5 * arcToCake(0),
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
 
         // Test min+max reversed.
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 4ce268f..ddfa05c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -140,6 +140,8 @@
         report1.numPositiveRegulations = 10;
         report1.cumulativeNegativeRegulations = 11;
         report1.numNegativeRegulations = 12;
+        report1.screenOffDurationMs = 13;
+        report1.screenOffDischargeMah = 14;
         mReports.add(report1);
         mScribeUnderTest.writeImmediatelyForTesting();
         mScribeUnderTest.loadFromDiskLocked();
@@ -160,6 +162,8 @@
         report2.numPositiveRegulations = 100;
         report2.cumulativeNegativeRegulations = 110;
         report2.numNegativeRegulations = 120;
+        report2.screenOffDurationMs = 130;
+        report2.screenOffDischargeMah = 140;
         mReports.add(report2);
         mScribeUnderTest.writeImmediatelyForTesting();
         mScribeUnderTest.loadFromDiskLocked();
@@ -385,6 +389,10 @@
                     eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations);
             assertEquals("Reports #" + i + " numNegativeRegulations are not equal",
                     eReport.numNegativeRegulations, aReport.numNegativeRegulations);
+            assertEquals("Reports #" + i + " screenOffDurationMs are not equal",
+                    eReport.screenOffDurationMs, aReport.screenOffDurationMs);
+            assertEquals("Reports #" + i + " screenOffDischargeMah are not equal",
+                    eReport.screenOffDischargeMah, aReport.screenOffDischargeMah);
         }
     }
 
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 7ae70eb..6551bde 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -113,7 +113,7 @@
     <uses-sdk android:minSdkVersion="1"
          android:targetSdkVersion="26"/>
 
-    <application android:testOnly="true">
+    <application android:testOnly="true" android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService"
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 19df5a2..57f5777 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -306,6 +306,18 @@
     }
 
     @Test
+    public void setServiceInfo_ChangeAccessibilityTool_updateFails() {
+        assertFalse(mSpyServiceInfo.isAccessibilityTool());
+
+        final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
+        serviceInfo.setAccessibilityTool(true);
+        mServiceConnection.setServiceInfo(serviceInfo);
+
+        // isAccessibilityTool should not be dynamically updatable
+        assertFalse(mSpyServiceInfo.isAccessibilityTool());
+    }
+
+    @Test
     public void canReceiveEvents_hasEventType_returnTrue() {
         final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
         updateServiceInfo(serviceInfo,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index e165f06..464fee2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -24,7 +24,6 @@
 import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
 import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
 import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
-import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_SOFTWARE_CURSOR;
 import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
 import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
 
@@ -53,7 +52,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
-import com.android.server.accessibility.cursor.SoftwareCursorGestureHandler;
 import com.android.server.accessibility.gestures.TouchExplorer;
 import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
@@ -85,7 +83,6 @@
     private final ArrayList<Display> mDisplayList = new ArrayList<>();
     private final int mFeatures = FLAG_FEATURE_AUTOCLICK
             | FLAG_FEATURE_TOUCH_EXPLORATION
-            | FLAG_FEATURE_SOFTWARE_CURSOR
             | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
             | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
             | FLAG_FEATURE_INJECT_MOTION_EVENTS
@@ -94,8 +91,8 @@
     // The expected order of EventStreamTransformations.
     private final Class[] mExpectedEventHandlerTypes =
             {KeyboardInterceptor.class, MotionEventInjector.class,
-                    SoftwareCursorGestureHandler.class, FullScreenMagnificationGestureHandler.class,
-                    TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class};
+                    FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
+                    AutoclickController.class, AccessibilityInputFilter.class};
 
     @Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
     @Mock private WindowManagerInternal mMockWindowManagerService;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 9475d0f..ed0336a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -149,7 +149,6 @@
         mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setDisplayMagnificationEnabledLocked(true);
-        mUserState.setSoftwareCursorEnabledLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -172,7 +171,6 @@
         assertNull(mUserState.getTargetAssignedToAccessibilityButton());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
-        assertFalse(mUserState.isSoftwareCursorEnabledLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 3c17102..0d6f326 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -37,11 +37,14 @@
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
 import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 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.any;
@@ -62,7 +65,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -74,6 +76,8 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.IntArray;
+import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
@@ -116,6 +120,7 @@
     private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
 
     private static final int TEST_UID = 11111;
+    private static final int USER_ID = 666;
 
     private static final long TEST_PROC_STATE_SEQ1 = 555;
     private static final long TEST_PROC_STATE_SEQ2 = 556;
@@ -147,8 +152,8 @@
     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
 
     private Context mContext = getInstrumentation().getTargetContext();
+
     @Mock private AppOpsService mAppOpsService;
-    @Mock private PackageManager mPackageManager;
 
     private TestInjector mInjector;
     private ActivityManagerService mAms;
@@ -828,6 +833,57 @@
                 true); // expectWait
     }
 
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+        int [] displayIds = mAms.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(4, 8, 15, 16, 23, 42);
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_invalidDisplay() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 666));
+
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays).isEmpty();
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_failed() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+        mInjector.returnValueForstartUserOnSecondaryDisplay = false;
+
+        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        Log.v(TAG, "Started: " + started);
+
+        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+                .that(started).isFalse();
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays)
+                .containsExactly(new Pair<>(USER_ID, 42));
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_success() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+        mInjector.returnValueForstartUserOnSecondaryDisplay = true;
+
+        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        Log.v(TAG, "Started: " + started);
+
+        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+                .that(started).isTrue();
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays)
+                .containsExactly(new Pair<>(USER_ID, 42));
+    }
+
     private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
             long lastNetworkUpdatedProcStateSeq,
             final long procStateSeqToWait, boolean expectWait) throws Exception {
@@ -922,7 +978,11 @@
     }
 
     private class TestInjector extends Injector {
-        private boolean mRestricted = true;
+        public boolean restricted = true;
+        public int[] secondaryDisplayIdsForStartingBackgroundUsers;
+
+        public boolean returnValueForstartUserOnSecondaryDisplay;
+        public List<Pair<Integer, Integer>> usersStartedOnSecondaryDisplays = new ArrayList<>();
 
         TestInjector(Context context) {
             super(context);
@@ -940,11 +1000,18 @@
 
         @Override
         public boolean isNetworkRestrictedForUid(int uid) {
-            return mRestricted;
+            return restricted;
         }
 
-        public void setNetworkRestrictedForUid(boolean restricted) {
-            mRestricted = restricted;
+        @Override
+        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+            return secondaryDisplayIdsForStartingBackgroundUsers;
+        }
+
+        @Override
+        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+            usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
+            return returnValueForstartUserOnSecondaryDisplay;
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 693bc7d..574aaf0 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -24,10 +24,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.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.Bundle;
 import android.os.Handler;
 import android.provider.Settings;
@@ -99,6 +101,9 @@
         // To prevent NullPointerException at the constructor of ActivityManagerConstants.
         when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
         when(mResources.getIntArray(anyInt())).thenReturn(new int[0]);
+        final TypedArray mockTypedArray = mock(TypedArray.class);
+        when(mockTypedArray.length()).thenReturn(1);
+        when(mResources.obtainTypedArray(anyInt())).thenReturn(mockTypedArray);
 
         mAms = new ActivityManagerService(new TestInjector(mContext),
                 mServiceThreadRule.getThread());
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 81f899c..2d2c76c 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -94,6 +94,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
@@ -679,7 +680,7 @@
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
         startBackgroundUserAssertions();
-        verifyUserAssignedToDisplay(TEST_USER_ID1, UserManagerInternal.PARENT_DISPLAY);
+        verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
@@ -834,8 +835,7 @@
     private void setUpAndStartUserInBackground(int userId) throws Exception {
         setUpUser(userId, 0);
         mUserController.startUser(userId, /* foreground= */ false);
-        verify(mInjector.mStorageManagerMock, times(1))
-                .unlockUserKey(userId, /* serialNumber= */ 0, /* secret= */ null);
+        verify(mInjector.mLockPatternUtilsMock, times(1)).unlockUserKeyIfUnsecured(userId);
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
@@ -843,8 +843,7 @@
         setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertThat(mUserController.startProfile(userId)).isTrue();
 
-        verify(mInjector.mStorageManagerMock, times(1))
-                .unlockUserKey(userId, /* serialNumber= */ 0, /* secret= */ null);
+        verify(mInjector.mLockPatternUtilsMock, times(1)).unlockUserKeyIfUnsecured(userId);
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
@@ -966,6 +965,7 @@
         private final UserManagerInternal mUserManagerInternalMock;
         private final WindowManagerService mWindowManagerMock;
         private final KeyguardManager mKeyguardManagerMock;
+        private final LockPatternUtils mLockPatternUtilsMock;
 
         private final Context mCtx;
 
@@ -982,6 +982,7 @@
             mStorageManagerMock = mock(IStorageManager.class);
             mKeyguardManagerMock = mock(KeyguardManager.class);
             when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
+            mLockPatternUtilsMock = mock(LockPatternUtils.class);
         }
 
         @Override
@@ -1081,6 +1082,11 @@
         protected void dismissKeyguard(Runnable runnable, String reason) {
             runnable.run();
         }
+
+        @Override
+        protected LockPatternUtils getLockPatternUtils() {
+            return mLockPatternUtilsMock;
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
new file mode 100644
index 0000000..47b4bf5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.sensors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.biometrics.BiometricManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AuthResultCoordinatorTest {
+    private AuthResultCoordinator mAuthResultCoordinator;
+
+    @Before
+    public void setUp() throws Exception {
+        mAuthResultCoordinator = new AuthResultCoordinator();
+    }
+
+    @Test
+    public void testDefaultMessage() {
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.FAILED,
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+    }
+
+    @Test
+    public void testSingleMessageCoordinator() {
+        mAuthResultCoordinator.authenticatedFor(
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.AUTHENTICATED,
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+    }
+
+    @Test
+    public void testLockout() {
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.LOCKED_OUT,
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+    }
+
+    @Test
+    public void testHigherStrengthPrecedence() {
+        mAuthResultCoordinator.authenticatedFor(
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+        mAuthResultCoordinator.authenticatedFor(
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.AUTHENTICATED,
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+
+        mAuthResultCoordinator.authenticatedFor(
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.AUTHENTICATED,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void testAuthPrecedence() {
+        mAuthResultCoordinator.authenticatedFor(
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+        checkResult(mAuthResultCoordinator.getResult(),
+                AuthResult.AUTHENTICATED,
+                BiometricManager.Authenticators.BIOMETRIC_WEAK);
+
+    }
+
+    void checkResult(AuthResult res, int status,
+            @BiometricManager.Authenticators.Types int strength) {
+        assertThat(res.getStatus()).isEqualTo(status);
+        assertThat(res.getBiometricStrength()).isEqualTo(strength);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
new file mode 100644
index 0000000..9bb0f58
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
@@ -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.biometrics.sensors;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+
+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;
+
+@Presubmit
+@SmallTest
+public class AuthSessionCoordinatorTest {
+    private static final int PRIMARY_USER = 0;
+    private static final int SECONDARY_USER = 10;
+
+    private AuthSessionCoordinator mCoordinator;
+
+    @Before
+    public void setUp() throws Exception {
+        mCoordinator = new AuthSessionCoordinator();
+    }
+
+    @Test
+    public void testUserUnlocked() {
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+
+        mCoordinator.authStartedFor(PRIMARY_USER, 1);
+        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1);
+
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+    }
+
+    @Test
+    public void testUserCanAuthDuringLockoutOfSameSession() {
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG);
+
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+
+        mCoordinator.authStartedFor(PRIMARY_USER, 1);
+        mCoordinator.authStartedFor(PRIMARY_USER, 2);
+        mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2);
+
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+    }
+
+    @Test
+    public void testMultiUserAuth() {
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG);
+
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+
+        mCoordinator.authStartedFor(PRIMARY_USER, 1);
+        mCoordinator.authStartedFor(PRIMARY_USER, 2);
+        mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2);
+
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
new file mode 100644
index 0000000..8baa1ce
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class MultiBiometricLockoutStateTest {
+    private static final int PRIMARY_USER = 0;
+    private MultiBiometricLockoutState mCoordinator;
+
+    private void unlockAllBiometrics() {
+        unlockAllBiometrics(mCoordinator, PRIMARY_USER);
+    }
+
+    private void lockoutAllBiometrics() {
+        lockoutAllBiometrics(mCoordinator, PRIMARY_USER);
+    }
+
+    private static void unlockAllBiometrics(MultiBiometricLockoutState coordinator, int userId) {
+        coordinator.onUserUnlocked(userId, BIOMETRIC_STRONG);
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue();
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue();
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue();
+    }
+
+    private static void lockoutAllBiometrics(MultiBiometricLockoutState coordinator, int userId) {
+        coordinator.onUserLocked(userId, BIOMETRIC_STRONG);
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse();
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse();
+        assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mCoordinator = new MultiBiometricLockoutState();
+    }
+
+    @Test
+    public void testInitialStateLockedOut() {
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+
+    @Test
+    public void testConvenienceLockout() {
+        unlockAllBiometrics();
+        mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+
+    @Test
+    public void testWeakLockout() {
+        unlockAllBiometrics();
+        mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_WEAK);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+
+    @Test
+    public void testStrongLockout() {
+        unlockAllBiometrics();
+        mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_STRONG);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+
+    @Test
+    public void testConvenienceUnlock() {
+        lockoutAllBiometrics();
+        mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+    }
+
+    @Test
+    public void testWeakUnlock() {
+        lockoutAllBiometrics();
+        mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_WEAK);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+    }
+
+    @Test
+    public void testStrongUnlock() {
+        lockoutAllBiometrics();
+        mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_STRONG);
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+    }
+
+    @Test
+    public void multiUser_userOneDoesNotAffectUserTwo() {
+        final int userOne = 1;
+        final int userTwo = 2;
+        MultiBiometricLockoutState coordinator = new MultiBiometricLockoutState();
+        lockoutAllBiometrics(coordinator, userOne);
+        lockoutAllBiometrics(coordinator, userTwo);
+
+        coordinator.onUserUnlocked(userOne, BIOMETRIC_WEAK);
+        assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse();
+        assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue();
+        assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue();
+
+        assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse();
+        assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse();
+        assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse();
+    }
+}
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 5d9d765..6b8c26d 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
@@ -25,7 +25,6 @@
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,6 +38,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
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 ef203d0..6388c7d 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
@@ -56,7 +56,6 @@
 import android.graphics.Point;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -84,6 +83,7 @@
 
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -236,10 +236,9 @@
                 .setBlockedActivities(getBlockedActivities())
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {
-                }, mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback,
-                params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
+                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
+                mActivityListener, mRunningAppsChangedCallback, params);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index c771998..3f0022d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -225,6 +226,11 @@
         AlarmManager getAlarmManager() {return services.alarmManager;}
 
         @Override
+        AlarmManagerInternal getAlarmManagerInternal() {
+            return services.alarmManagerInternal;
+        }
+
+        @Override
         LockPatternUtils newLockPatternUtils() {
             return services.lockPatternUtils;
         }
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 171b895..ddb3049 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -60,6 +60,7 @@
 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.EscrowTokenStateChangeCallback;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
 import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
@@ -4501,7 +4502,8 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
         dpm.setTimeZone(admin1, "Asia/Shanghai");
-        verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+        verify(getServices().alarmManagerInternal)
+                .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString());
     }
 
     @Test
@@ -4516,7 +4518,8 @@
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
         dpm.setTimeZone(admin1, "Asia/Shanghai");
-        verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+        verify(getServices().alarmManagerInternal)
+                .setTimeZone(eq("Asia/Shanghai"), eq(TIME_ZONE_CONFIDENCE_HIGH), anyString());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 46cc68f..cec9d50 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -73,6 +73,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.UserManagerInternal;
@@ -124,6 +125,7 @@
     public final ConnectivityManager connectivityManager;
     public final AccountManager accountManager;
     public final AlarmManager alarmManager;
+    public final AlarmManagerInternal alarmManagerInternal;
     public final KeyChain.KeyChainConnection keyChainConnection;
     public final CrossProfileApps crossProfileApps;
     public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
@@ -176,6 +178,7 @@
         connectivityManager = mock(ConnectivityManager.class);
         accountManager = mock(AccountManager.class);
         alarmManager = mock(AlarmManager.class);
+        alarmManagerInternal = mock(AlarmManagerInternal.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
         crossProfileApps = mock(CrossProfileApps.class);
         persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 3f4148b..a45144e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -588,6 +588,157 @@
         });
     }
 
+    @Test
+    public void requestBaseStateOverride() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+        flushHandler();
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestBaseStateOverride(token,
+                OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+        // Flush the handler twice. The first flush ensures the request is added and the policy is
+        // notified, while the second flush ensures the callback is notified once the change is
+        // committed.
+        flushHandler(2 /* count */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        assertNotNull(callback.getLastNotifiedInfo());
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mService.getBinderService().cancelBaseStateOverride();
+        flushHandler();
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set back to the requested state once the override is cleared.
+        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+        assertFalse(mService.getOverrideBaseState().isPresent());
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
+
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                DEFAULT_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
+        final DeviceState testDeviceState = new DeviceState(2, "TEST", 0);
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+        mProvider.notifySupportedDeviceStates(
+                new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState });
+        flushHandler();
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestBaseStateOverride(token,
+                OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+        // Flush the handler twice. The first flush ensures the request is added and the policy is
+        // notified, while the second flush ensures the callback is notified once the change is
+        // committed.
+        flushHandler(2 /* count */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        assertNotNull(callback.getLastNotifiedInfo());
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mProvider.setState(testDeviceState.getIdentifier());
+        flushHandler();
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set to the new base state once the override is cleared.
+        assertEquals(mService.getCommittedState(), Optional.of(testDeviceState));
+        assertEquals(mSysPropSetter.getValue(),
+                testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
+        assertEquals(mService.getBaseState(), Optional.of(testDeviceState));
+        assertFalse(mService.getOverrideBaseState().isPresent());
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                testDeviceState.getIdentifier());
+
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                testDeviceState.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                testDeviceState.getIdentifier());
+    }
+
+    @Test
+    public void requestBaseState_unsupportedState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token,
+                    UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestBaseState_invalidState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE,
+                    0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestBaseState_beforeRegisteringCallback() {
+        assertThrows(IllegalStateException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token,
+                    DEFAULT_DEVICE_STATE.getIdentifier(),
+                    0 /* flags */);
+        });
+    }
+
     private static void assertArrayEquals(int[] expected, int[] actual) {
         Assert.assertTrue(Arrays.equals(expected, actual));
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 2297c91..430504c 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicestate;
 
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
 
@@ -57,7 +59,7 @@
     @Test
     public void addRequest() {
         OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
         mController.addRequest(request);
@@ -67,14 +69,14 @@
     @Test
     public void addRequest_cancelExistingRequestThroughNewRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
         mController.addRequest(secondRequest);
@@ -85,7 +87,7 @@
     @Test
     public void addRequest_cancelActiveRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
 
@@ -97,30 +99,90 @@
     }
 
     @Test
-    public void handleBaseStateChanged() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */,
-                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
+    public void addBaseStateRequest() {
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(request));
 
-        mController.addRequest(firstRequest);
+        mController.addBaseStateRequest(request);
+        assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE);
+    }
+
+    @Test
+    public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(firstRequest));
+
+        mController.addBaseStateRequest(firstRequest);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(secondRequest));
+
+        mController.addBaseStateRequest(secondRequest);
+        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+    }
+
+    @Test
+    public void addBaseStateRequest_cancelActiveBaseStateRequest() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+        mController.addBaseStateRequest(firstRequest);
 
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleBaseStateChanged();
+        mController.cancelBaseStateOverrideRequest();
 
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
+    public void handleBaseStateChanged() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */,
+                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
+                OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+        mController.addRequest(firstRequest);
+
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        mController.addBaseStateRequest(baseStateRequest);
+
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+        mController.handleBaseStateChanged(1);
+
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
+    }
+
+    @Test
     public void handleProcessDied() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
         mController.handleProcessDied(0);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
@@ -128,13 +190,20 @@
         mController.setStickyRequestsAllowed(true);
 
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
         mController.handleProcessDied(0);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
 
         mController.cancelStickyRequest();
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
@@ -143,22 +212,31 @@
     @Test
     public void handleNewSupportedStates() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{ 0, 1 });
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{ 0 });
+        mController.handleNewSupportedStates(new int[]{0, 1});
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+        mController.handleNewSupportedStates(new int[]{0});
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void cancelOverrideRequestsTest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..0206839 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -38,7 +38,6 @@
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -56,7 +55,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessControllerTest {
     private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
@@ -422,13 +420,13 @@
 
     @Test
     public void testHysteresisLevels() {
-        int[] ambientBrighteningThresholds = {100, 200};
-        int[] ambientDarkeningThresholds = {400, 500};
-        int[] ambientThresholdLevels = {500};
+        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,
+                ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
                 ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
 
         // test low, activate minimum change thresholds.
@@ -437,16 +435,17 @@
         assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
 
         // test max
-        assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
-        assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+        // 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(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
-        assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+        assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+        assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
 
         // test at (considered above) threshold
-        assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
-        assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+        assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+        assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index f352de4..89ff2c2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -48,7 +48,7 @@
 @RunWith(AndroidJUnit4.class)
 public class BrightnessMappingStrategyTest {
 
-    private static final int[] LUX_LEVELS = {
+    private static final float[] LUX_LEVELS = {
         0,
         5,
         20,
@@ -126,7 +126,8 @@
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
     private static final float MAXIMUM_GAMMA = 3.0f;
-    private static final int[] GAMMA_CORRECTION_LUX = {
+
+    private static final float[] GAMMA_CORRECTION_LUX = {
         0,
         100,
         1000,
@@ -155,7 +156,7 @@
 
     @Test
     public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
@@ -170,7 +171,7 @@
 
     @Test
     public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
@@ -179,13 +180,13 @@
             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
             assertTrue("Desired brightness should be between adjacent control points.",
                     backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
-                        && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
+                            && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
         }
     }
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
@@ -200,7 +201,7 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
@@ -214,8 +215,10 @@
 
     @Test
     public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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++) {
@@ -231,8 +234,9 @@
 
     @Test
     public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+        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 =
@@ -248,11 +252,12 @@
 
     @Test
     public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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[] lux = {0f, 1f};
         final float[] nits = {
                 DISPLAY_RANGE_NITS[0],
                 DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
@@ -273,8 +278,9 @@
 
     @Test
     public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS);
+        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++) {
@@ -304,28 +310,30 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
-                DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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 int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
+        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
         final int idx = lux.length / 2;
-        int tmp = lux[idx];
-        lux[idx] = lux[idx+1];
-        lux[idx+1] = tmp;
-        Resources res = createResources(lux, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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];
-        res = createResources(lux, DISPLAY_LEVELS_NITS);
+        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);
     }
@@ -333,62 +341,64 @@
     @Test
     public void testDifferentNumberOfControlPointValuesFails() {
         //Extra lux level
-        final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1);
+        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(lux, DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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(lux, DISPLAY_LEVELS_BACKLIGHT);
+        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);
+                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources(LUX_LEVELS, backlight);
+        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);
+        final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
         nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources(LUX_LEVELS, nits);
+        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(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS);
+        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(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS);
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
         physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(physical);
 
-        res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS);
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
         physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(physical);
     }
 
     @Test
     public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
-                DISPLAY_LEVELS_NITS);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+        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(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
     }
 
@@ -468,42 +478,19 @@
         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
     }
 
-    private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) {
-        return createResources(luxLevels, brightnessLevelsBacklight,
-                EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/);
+    private Resources createResources(int[] brightnessLevelsBacklight) {
+        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
     }
 
-    private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits) {
-        return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
-                brightnessLevelsNits);
+    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
+        return createResources(EMPTY_INT_ARRAY,
+                luxLevelsIdle, brightnessLevelsNitsIdle);
     }
 
-    private Resources createResourcesIdle(int[] luxLevels, float[] brightnessLevelsNits) {
-        return createResources(EMPTY_INT_ARRAY, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY,
-                luxLevels, brightnessLevelsNits);
-    }
-
-    private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
-            float[] brightnessLevelsNits) {
-        return createResources(luxLevels, brightnessLevelsBacklight, brightnessLevelsNits,
-                EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
-
-    }
-
-    private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
-            float[] brightnessLevelsNits, int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
+    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
+            float[] brightnessLevelsNitsIdle) {
 
         Resources mockResources = mock(Resources.class);
-
-        // For historical reasons, the lux levels resource implicitly defines the first point as 0,
-        // so we need to chop it off of the array the mock resource object returns.
-        // Don't mock if these values are not set. If we try to use them, we will fail.
-        if (luxLevels.length > 0) {
-            int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
-            when(mockResources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevels))
-                    .thenReturn(luxLevelsResource);
-        }
         if (luxLevelsIdle.length > 0) {
             int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
                     luxLevelsIdle.length);
@@ -516,10 +503,6 @@
                 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
                 .thenReturn(brightnessLevelsBacklight);
 
-        TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits);
-        when(mockResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(mockBrightnessLevelNits);
         TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
         when(mockResources.obtainTypedArray(
                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
@@ -549,6 +532,18 @@
         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;
     }
 
@@ -590,8 +585,10 @@
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
 
-        Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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:
@@ -619,8 +616,10 @@
         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(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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:
@@ -645,8 +644,10 @@
     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(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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 */);
@@ -667,8 +668,10 @@
         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(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
-        DisplayDeviceConfig ddc = createDdc();
+        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:
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 6a6cd6c..800f60b 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -35,7 +35,6 @@
 import android.os.Temperature;
 import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,7 +56,6 @@
 import java.util.List;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BrightnessThrottlerTest {
     private static final float EPSILON = 0.000001f;
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
index 26a83a2..53d8de0c 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
@@ -24,7 +24,6 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -39,7 +38,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ColorFadeTest {
     private static final int DISPLAY_ID = 123;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..a6a2419 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -27,11 +27,12 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.platform.test.annotations.Presubmit;
 
 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;
@@ -44,10 +45,11 @@
 import java.nio.file.Path;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DisplayDeviceConfigTest {
     private DisplayDeviceConfig mDisplayDeviceConfig;
+    private static final float ZERO_DELTA = 0.0f;
+    private static final float SMALL_DELTA = 0.0001f;
     @Mock
     private Context mContext;
 
@@ -69,26 +71,74 @@
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
-        assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+        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},
-                0.0f);
-        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.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},
-                0.0f);
-        assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
-        assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+                ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.32f, 75.43f}, 0.0f);
+                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);
+
+
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -97,9 +147,61 @@
     public void testConfigValuesFromConfigResource() {
         setupDisplayDeviceConfigFromConfigResourceFile();
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+                float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+                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);
+
+
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -154,11 +256,126 @@
                 +   "<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>2</minimum>\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>"
@@ -171,18 +388,6 @@
                 +   "</screenBrightnessRampDecreaseMaxMillis>"
                 +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
                 +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
-                +   "<displayBrightnessChangeThresholds>"
-                +       "<brighteningThresholds>"
-                +           "<minimum>"
-                +               "0.001"
-                +           "</minimum>"
-                +       "</brighteningThresholds>"
-                +       "<darkeningThresholds>"
-                +           "<minimum>"
-                +               "0.002"
-                +           "</minimum>"
-                +       "</darkeningThresholds>"
-                +   "</displayBrightnessChangeThresholds>"
                 +   "<screenBrightnessRampIncreaseMaxMillis>"
                 +       "2000"
                 +    "</screenBrightnessRampIncreaseMaxMillis>\n"
@@ -241,8 +446,24 @@
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(screenBrightnessLevelLux);
 
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+        // 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});
 
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 3eb1dea..3c7bb2a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -53,12 +53,10 @@
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
-import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.MessageQueue;
 import android.os.Process;
-import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
@@ -78,6 +76,7 @@
 import com.android.server.SystemService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 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;
@@ -106,7 +105,6 @@
 import java.util.stream.LongStream;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 53fa3e2..a1e5ce7 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -22,14 +22,10 @@
 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.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;
@@ -51,7 +47,6 @@
 import android.os.Temperature;
 import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 import android.test.mock.MockContentResolver;
 import android.util.MathUtils;
 
@@ -76,7 +71,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class HighBrightnessModeControllerTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index cc68ba8..0b33c30 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -45,7 +45,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
@@ -67,7 +66,6 @@
 import java.util.Set;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class LogicalDisplayMapperTest {
     private static int sUniqueTestDisplayId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index b0738fd..5a43530 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -26,7 +26,6 @@
 
 import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
-import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -40,7 +39,6 @@
 import java.io.OutputStream;
 
 @SmallTest
-@Presubmit
 public class LogicalDisplayTest {
     private static final int DISPLAY_ID = 0;
     private static final int LAYER_STACK = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
index 9f1a209..92d8abd 100644
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
@@ -3,18 +3,10 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {
-          "include-filter": "com.android.server.display."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
+        {"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/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index f69c5c2..fabf535 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 
 import android.hardware.display.BrightnessInfo;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,7 +28,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class BrightnessEventTest {
     private BrightnessEvent mBrightnessEvent;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index ffc2e0d..57aa61a 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -28,7 +27,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class BrightnessReasonTest {
     private BrightnessReason mBrightnessReason;
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index d118661..5f3f3d7 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -22,6 +22,7 @@
 import android.hardware.BatteryState.STATUS_FULL
 import android.hardware.BatteryState.STATUS_UNKNOWN
 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
@@ -32,6 +33,12 @@
 import android.view.InputDevice
 import androidx.test.InstrumentationRegistry
 import com.android.server.input.BatteryController.UEventManager
+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.fail
@@ -42,7 +49,6 @@
 import org.mockito.ArgumentMatchers.notNull
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
@@ -52,7 +58,9 @@
 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): InputDevice =
     InputDevice.Builder()
@@ -64,6 +72,64 @@
         .setGeneration(0)
         .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)
+}
+
+// Helper 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
+) {
+    verify(this, mode).onBatteryStateChanged(
+        MockitoHamcrest.argThat(matchesState(deviceId, isPresent, status, capacity, eventTime)))
+}
+
 /**
  * Tests for {@link InputDeviceBatteryController}.
  *
@@ -184,14 +250,12 @@
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
         val listener = createMockListener()
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            eq(STATUS_FULL), eq(1f), anyLong())
+        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)
-        verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
-            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+        listener.verifyNotified(SECOND_DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
     }
 
     @Test
@@ -203,14 +267,13 @@
         val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
         verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
 
         // If the battery state has changed when an UEvent is sent, the listeners are notified.
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
         uEventListener.value!!.onUEvent(TIMESTAMP)
-        verify(listener).onBatteryStateChanged(DEVICE_ID, true /*isPresent*/, STATUS_CHARGING,
-            0.80f, 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.
@@ -233,16 +296,15 @@
         val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
         verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
 
         // If the battery presence for the InputDevice changes, the listener is notified.
         `when`(iInputManager.getInputDevice(DEVICE_ID))
             .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false))
         notifyDeviceChanged(DEVICE_ID)
         testLooper.dispatchNext()
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(false /*isPresent*/),
-            eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong())
+        listener.verifyNotified(DEVICE_ID, isPresent = false, status = STATUS_UNKNOWN,
+            capacity = Float.NaN)
         // Since the battery is no longer present, the UEventListener should be removed.
         verify(uEventManager).removeListener(uEventListener.value)
 
@@ -251,10 +313,85 @@
             .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true))
         notifyDeviceChanged(DEVICE_ID)
         testLooper.dispatchNext()
-        verify(listener, times(2)).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+        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(BatteryController.POLLING_PERIOD_MILLIS - 1)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, 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(BatteryController.POLLING_PERIOD_MILLIS)
+        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(BatteryController.POLLING_PERIOD_MILLIS)
+        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)
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index 844f5d4..e390bcc 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,13 +19,14 @@
 import android.content.Context
 import android.content.ContextWrapper
 import android.hardware.display.DisplayViewport
-import android.hardware.input.InputManagerInternal
 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 java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -35,7 +36,6 @@
 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
@@ -43,9 +43,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 /**
  * Tests for {@link InputManagerService}.
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
new file mode 100644
index 0000000..44bdf5e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.InputManager
+import android.hardware.lights.Light
+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_LEVELS
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+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.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+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
+    }
+
+    @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()
+
+    @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)
+        val inputManager = InputManager.resetInstance(iInputManager)
+        `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())).then {
+            val args = it.arguments
+            lightColorMap.getOrDefault(args[1] as Int, -1)
+        }
+        lightColorMap.clear()
+    }
+
+    @After
+    fun tearDown() {
+        InputManager.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)
+        // Initially backlight is at min
+        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
+
+        val brightnessLevelsArray = BRIGHTNESS_LEVELS.toTypedArray()
+        for (level in 1 until brightnessLevelsArray.size) {
+            keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+            testLooper.dispatchNext()
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(brightnessLevelsArray[level], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                brightnessLevelsArray[level],
+                dataStore.getKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID
+                ).asInt
+            )
+        }
+
+        for (level in brightnessLevelsArray.size - 2 downTo 0) {
+            keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
+            testLooper.dispatchNext()
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(brightnessLevelsArray[level], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                brightnessLevelsArray[level],
+                dataStore.getKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID
+                ).asInt
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightIncrementAboveMaxLevel() {
+        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)
+        // Initially backlight is at max
+        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0)
+
+        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+        testLooper.dispatchNext()
+        assertEquals(
+            "Light value for max level mismatched",
+            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
+        assertEquals(
+            "Light value for max level must be correctly stored in the datastore",
+            BRIGHTNESS_LEVELS.last(),
+            dataStore.getKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID
+            ).asInt
+        )
+    }
+
+    @Test
+    fun testKeyboardBacklightDecrementBelowMin() {
+        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)
+        // Initially backlight is at min
+        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
+
+        keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
+        testLooper.dispatchNext()
+        assertEquals(
+            "Light value for min level mismatched",
+            Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
+        assertEquals(
+            "Light value for min level must be correctly stored in the datastore",
+            BRIGHTNESS_LEVELS.first(),
+            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)
+
+        keyboardBacklightController.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)
+
+        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+        testLooper.dispatchNext()
+        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))
+
+        dataStore.setKeyboardBacklightBrightness(
+            keyboardWithBacklight.descriptor,
+            LIGHT_ID,
+            BRIGHTNESS_LEVELS.last()
+        )
+        lightColorMap.clear()
+
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        assertEquals(
+            "Keyboard backlight level should be restored to the level saved in the data store",
+            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            lightColorMap[LIGHT_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,
+            BRIGHTNESS_LEVELS.last()
+        )
+        lightColorMap.clear()
+
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        assertTrue(
+            "Keyboard backlight should not be changed until its added",
+            lightColorMap.isEmpty()
+        )
+
+        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
+        assertEquals(
+            "Keyboard backlight level should be restored to the level saved in the data store",
+            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 0a4da8d..426b943 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -35,11 +35,14 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -1210,4 +1213,79 @@
                 Build.VERSION_CODES.P,
                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
     }
+
+    private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
+        final IntArray subtypes = new IntArray();
+        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                new TextUtils.SimpleStringSplitter(';');
+        if (TextUtils.isEmpty(subtypeHashCodesStr)) {
+            return subtypes;
+        }
+        imeSubtypeSplitter.setString(subtypeHashCodesStr);
+        while (imeSubtypeSplitter.hasNext()) {
+            subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
+        }
+        return subtypes;
+    }
+
+    private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
+            @NonNull String initialEnabledImeStr, @NonNull String imeId,
+            @NonNull String enabledSubtypeHashCodesStr) {
+        assertEquals(expectedEnabledImeStr,
+                InputMethodUtils.InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
+                        imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
+    }
+
+    @Test
+    public void updateEnabledImeStringTest() {
+        // No change cases
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+
+        // To enable subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1",
+                "com.android/.ime1", "com.android/.ime1", "1");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3",
+                "com.android/.ime1", "com.android/.ime1", "1;2;3");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3",
+                "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
+                "1;2;3");
+
+        // To reset enabled subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1:com.android/.ime2",
+                "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1",
+                "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
+                "");
+    }
 }
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 e220841..c934e65 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -168,16 +168,15 @@
         allUsers.add(SECONDARY_USER_INFO);
         when(mUserManager.getUsers()).thenReturn(allUsers);
 
-        when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
-                new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+        when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer(
+            invocation -> {
                 Object[] args = invocation.getArguments();
-                mStorageManager.unlockUser((int)args[0], (byte[])args[2],
-                        (IProgressListener) args[3]);
+                int userId = (int) args[0];
+                IProgressListener listener = (IProgressListener) args[1];
+                listener.onStarted(userId, null);
+                listener.onFinished(userId, null);
                 return true;
-            }
-        });
+            });
 
         // Adding a fake Device Owner app which will enable escrow token support in LSS.
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
@@ -215,37 +214,20 @@
     private IStorageManager setUpStorageManagerMock() throws RemoteException {
         final IStorageManager sm = mock(IStorageManager.class);
 
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                mStorageManager.addUserKeyAuth((int) args[0] /* userId */,
-                        (int) args[1] /* serialNumber */,
-                        (byte[]) args[2] /* secret */);
-                return null;
-            }
-        }).when(sm).addUserKeyAuth(anyInt(), anyInt(), any());
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            mStorageManager.unlockUserKey(/* userId= */ (int) args[0],
+                    /* secret= */ (byte[]) args[2]);
+            return null;
+        }).when(sm).unlockUserKey(anyInt(), anyInt(), any());
 
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                mStorageManager.clearUserKeyAuth((int) args[0] /* userId */,
-                        (int) args[1] /* serialNumber */,
-                        (byte[]) args[2] /* secret */);
-                return null;
-            }
-        }).when(sm).clearUserKeyAuth(anyInt(), anyInt(), any());
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            mStorageManager.setUserKeyProtection(/* userId= */ (int) args[0],
+                    /* secret= */ (byte[]) args[1]);
+            return null;
+        }).when(sm).setUserKeyProtection(anyInt(), any());
 
-        doAnswer(
-                new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                Object[] args = invocation.getArguments();
-                mStorageManager.fixateNewestUserKeyAuth((int) args[0] /* userId */);
-                return null;
-            }
-        }).when(sm).fixateNewestUserKeyAuth(anyInt());
         return sm;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
index 619ef70..91f3fed 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
@@ -16,75 +16,26 @@
 
 package com.android.server.locksettings;
 
-import android.os.IProgressListener;
-import android.os.RemoteException;
+import static com.google.common.truth.Truth.assertThat;
+
 import android.util.ArrayMap;
 
-
-import junit.framework.AssertionFailedError;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
 public class FakeStorageManager {
 
-    private ArrayMap<Integer, ArrayList<byte[]>> mAuth = new ArrayMap<>();
-    private boolean mIgnoreBadUnlock;
+    private final ArrayMap<Integer, byte[]> mUserSecrets = new ArrayMap<>();
 
-    public void addUserKeyAuth(int userId, int serialNumber, byte[] secret) {
-        getUserAuth(userId).add(secret);
-    }
-
-    public void clearUserKeyAuth(int userId, int serialNumber, byte[] secret) {
-        ArrayList<byte[]> auths = getUserAuth(userId);
-        if (secret == null) {
-            return;
-        }
-        auths.remove(secret);
-        auths.add(null);
-    }
-
-    public void fixateNewestUserKeyAuth(int userId) {
-        ArrayList<byte[]> auths = mAuth.get(userId);
-        byte[] latest = auths.get(auths.size() - 1);
-        auths.clear();
-        auths.add(latest);
-    }
-
-    private ArrayList<byte[]> getUserAuth(int userId) {
-        if (!mAuth.containsKey(userId)) {
-            ArrayList<byte[]> auths = new ArrayList<>();
-            auths.add(null);
-            mAuth.put(userId, auths);
-        }
-        return mAuth.get(userId);
+    public void setUserKeyProtection(int userId, byte[] secret) {
+        assertThat(mUserSecrets).doesNotContainKey(userId);
+        mUserSecrets.put(userId, secret);
     }
 
     public byte[] getUserUnlockToken(int userId) {
-        ArrayList<byte[]> auths = getUserAuth(userId);
-        if (auths.size() != 1) {
-            throw new AssertionFailedError("More than one secret exists");
-        }
-        return auths.get(0);
+        byte[] secret = mUserSecrets.get(userId);
+        assertThat(secret).isNotNull();
+        return secret;
     }
 
-    public void unlockUser(int userId, byte[] secret, IProgressListener listener)
-            throws RemoteException {
-        listener.onStarted(userId, null);
-        listener.onFinished(userId, null);
-        ArrayList<byte[]> auths = getUserAuth(userId);
-        if (auths.size() > 1) {
-            throw new AssertionFailedError("More than one secret exists");
-        }
-        byte[] auth = auths.get(0);
-        if (!Arrays.equals(secret, auth)) {
-            if (!mIgnoreBadUnlock) {
-                throw new AssertionFailedError("Invalid secret to unlock user " + userId);
-            }
-        }
-    }
-
-    public void setIgnoreBadUnlock(boolean ignore) {
-        mIgnoreBadUnlock = ignore;
+    public void unlockUserKey(int userId, byte[] secret) {
+        assertThat(mUserSecrets.get(userId)).isEqualTo(secret);
     }
 }
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 3477288..3f259e3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -145,15 +145,9 @@
         // Verify that profile which aren't running (e.g. turn off work) don't get unlocked
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
-        /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
-         * credential as part of verifyCredential() before the new credential is committed in
-         * StorageManager. So we relax the check in our mock StorageManager to allow that.
-         */
-        mStorageManager.setIgnoreBadUnlock(true);
         // Change primary password and verify that profile SID remains
         assertTrue(mService.setLockCredential(
                 secondUnifiedPassword, firstUnifiedPassword, PRIMARY_USER_ID));
-        mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
@@ -172,15 +166,9 @@
         assertTrue(mService.setLockCredential(primaryPassword,
                 nonePassword(),
                 PRIMARY_USER_ID));
-        /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
-         * credential as part of verifyCredential() before the new credential is committed in
-         * StorageManager. So we relax the check in our mock StorageManager to allow that.
-         */
-        mStorageManager.setIgnoreBadUnlock(true);
         assertTrue(mService.setLockCredential(profilePassword,
                 nonePassword(),
                 MANAGED_PROFILE_USER_ID));
-        mStorageManager.setIgnoreBadUnlock(false);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -203,11 +191,8 @@
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
 
-        // Change primary credential and make sure we don't affect profile
-        mStorageManager.setIgnoreBadUnlock(true);
         assertTrue(mService.setLockCredential(
                 newPassword("pwd"), primaryPassword, PRIMARY_USER_ID));
-        mStorageManager.setIgnoreBadUnlock(false);
         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/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 87beece..5e6cccc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
@@ -126,6 +127,7 @@
         initializeCredential(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+        verify(mActivityManager).unlockUser2(eq(PRIMARY_USER_ID), any());
 
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
                 badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -187,32 +189,13 @@
     }
 
     @Test
-    public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
-        mService.onUnlockUser(PRIMARY_USER_ID);
-        flushHandlerTasks();
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
-    }
-
-    @Test
-    public void testCredentialDoesNotPassAuthSecret() throws RemoteException {
-        LockscreenCredential password = newPassword("password");
-        initializeCredential(password, PRIMARY_USER_ID);
-
-        reset(mAuthSecretService);
-        mService.onUnlockUser(PRIMARY_USER_ID);
-        flushHandlerTasks();
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
-    }
-
-    @Test
-    public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
+    public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         initializeCredential(password, PRIMARY_USER_ID);
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
-        mService.onUnlockUser(PRIMARY_USER_ID);
-        flushHandlerTasks();
+        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
index 6a27f39..2cd5314 100644
--- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
@@ -77,7 +77,7 @@
     private ILogd mLogdMock;
 
     private LogcatManagerService mService;
-    private LogcatManagerService.LogcatManagerServiceInternal mLocalService;
+    private LogcatManagerService.LogAccessDialogCallback mDialogCallback;
     private ContextWrapper mContextSpy;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
@@ -118,7 +118,7 @@
                 return mLogdMock;
             }
         });
-        mLocalService = mService.getLocalService();
+        mDialogCallback = mService.getDialogCallback();
         mService.onStart();
     }
 
@@ -207,7 +207,7 @@
         mTestLooper.dispatchAll();
         verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
 
-        mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -222,7 +222,7 @@
         mTestLooper.dispatchAll();
         verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
 
-        mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -240,7 +240,7 @@
         verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
         verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
 
-        mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -260,7 +260,7 @@
         verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
         verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
 
-        mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
@@ -275,7 +275,7 @@
                 ActivityManager.PROCESS_STATE_TOP);
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
         mTestLooper.dispatchAll();
-        mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
@@ -293,7 +293,7 @@
                 ActivityManager.PROCESS_STATE_TOP);
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
         mTestLooper.dispatchAll();
-        mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
@@ -313,7 +313,7 @@
                 ActivityManager.PROCESS_STATE_TOP);
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
         mTestLooper.dispatchAll();
-        mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2);
@@ -330,7 +330,7 @@
                 ActivityManager.PROCESS_STATE_TOP);
         mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
         mTestLooper.dispatchAll();
-        mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+        mDialogCallback.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
         mTestLooper.dispatchAll();
 
         advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 39220a4..f3ac246 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -749,8 +749,8 @@
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID());
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
         assertThat(testPkgSetting01.getFlags(), is(0));
         assertThat(testPkgSetting01.getPrivateFlags(), is(0));
         final PackageUserState userState = testPkgSetting01.readUserState(0);
@@ -785,8 +785,8 @@
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID());
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
         assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
         assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
         final PackageUserState userState = testPkgSetting01.readUserState(0);
@@ -860,8 +860,8 @@
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
         assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
         // signatures object must be different
         assertNotSame(testPkgSetting01.getSignatures(), originalSignatures);
         assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
@@ -901,8 +901,8 @@
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(0));
         assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("x86"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
         assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
         // by default, the package is considered stopped
         final PackageUserState userState = testPkgSetting01.readUserState(0);
@@ -944,8 +944,8 @@
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(0));
         assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("x86"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
         assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
         final PackageUserState userState = testPkgSetting01.readUserState(0);
         verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
@@ -987,8 +987,8 @@
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(0));
         assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
         assertNotSame(testPkgSetting01.getSignatures(), disabledSignatures);
         assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
         final PackageUserState userState = testPkgSetting01.readUserState(0);
@@ -1211,11 +1211,11 @@
         // assertThat(origPkgSetting.pkg, is(testPkgSetting.pkg));
         assertThat(origPkgSetting.getFlags(), is(testPkgSetting.getFlags()));
         assertThat(origPkgSetting.getPrivateFlags(), is(testPkgSetting.getPrivateFlags()));
-        assertSame(origPkgSetting.getPrimaryCpuAbi(), testPkgSetting.getPrimaryCpuAbi());
-        assertThat(origPkgSetting.getPrimaryCpuAbi(), is(testPkgSetting.getPrimaryCpuAbi()));
+        assertSame(origPkgSetting.getPrimaryCpuAbiLegacy(), testPkgSetting.getPrimaryCpuAbiLegacy());
+        assertThat(origPkgSetting.getPrimaryCpuAbiLegacy(), is(testPkgSetting.getPrimaryCpuAbiLegacy()));
         assertThat(origPkgSetting.getRealName(), is(testPkgSetting.getRealName()));
-        assertSame(origPkgSetting.getSecondaryCpuAbi(), testPkgSetting.getSecondaryCpuAbi());
-        assertThat(origPkgSetting.getSecondaryCpuAbi(), is(testPkgSetting.getSecondaryCpuAbi()));
+        assertSame(origPkgSetting.getSecondaryCpuAbiLegacy(), testPkgSetting.getSecondaryCpuAbiLegacy());
+        assertThat(origPkgSetting.getSecondaryCpuAbiLegacy(), is(testPkgSetting.getSecondaryCpuAbiLegacy()));
         assertSame(origPkgSetting.getSignatures(), testPkgSetting.getSignatures());
         assertThat(origPkgSetting.getSignatures(), is(testPkgSetting.getSignatures()));
         assertThat(origPkgSetting.getLastModifiedTime(), is(testPkgSetting.getLastModifiedTime()));
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 67eeb4e..68310f4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -1011,10 +1011,10 @@
                 .addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
                 .addImplicitPermission("foo25")
                 .addProtectedBroadcast("foo8")
-                .setSdkLibName("sdk12")
+                .setSdkLibraryName("sdk12")
                 .setSdkLibVersionMajor(42)
                 .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
-                .setStaticSharedLibName("foo23")
+                .setStaticSharedLibraryName("foo23")
                 .setStaticSharedLibVersion(100)
                 .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
                 .addLibraryName("foo10")
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 084f4f1..4d03749 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -211,8 +211,8 @@
 
         assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
 
-        assertThat(scanResult.mPkgSetting.getPrimaryCpuAbi(), is("primaryCpuAbi"));
-        assertThat(scanResult.mPkgSetting.getSecondaryCpuAbi(), is("secondaryCpuAbi"));
+        assertThat(scanResult.mPkgSetting.getPrimaryCpuAbiLegacy(), is("primaryCpuAbi"));
+        assertThat(scanResult.mPkgSetting.getSecondaryCpuAbiLegacy(), is("secondaryCpuAbi"));
         assertThat(scanResult.mPkgSetting.getCpuAbiOverride(), nullValue());
 
         assertPathsNotDerived(scanResult);
@@ -241,7 +241,7 @@
     @Test
     public void installSdkLibrary() throws Exception {
         final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("ogl.sdk_123")
-                .setSdkLibName("ogl.sdk")
+                .setSdkLibraryName("ogl.sdk")
                 .setSdkLibVersionMajor(123)
                 .hideAsParsed())
                 .setPackageName("ogl.sdk_123")
@@ -272,7 +272,7 @@
     @Test
     public void installStaticSharedLibrary() throws Exception {
         final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
-                .setStaticSharedLibName("static.lib")
+                .setStaticSharedLibraryName("static.lib")
                 .setStaticSharedLibVersion(123L)
                 .hideAsParsed())
                 .setPackageName("static.lib.pkg.123")
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 f567e80..c1e778d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -284,22 +284,8 @@
     @Test
     public void testRemoveUserByHandle() {
         UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
-        final UserHandle user = userInfo.getUserHandle();
-        synchronized (mUserRemoveLock) {
-            mUserManager.removeUser(user);
-            long time = System.currentTimeMillis();
-            while (mUserManager.getUserInfo(user.getIdentifier()) != null) {
-                try {
-                    mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS);
-                } catch (InterruptedException ie) {
-                    Thread.currentThread().interrupt();
-                    return;
-                }
-                if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) {
-                    fail("Timeout waiting for removeUser. userId = " + user.getIdentifier());
-                }
-            }
-        }
+
+        removeUser(userInfo.getUserHandle());
 
         assertThat(hasUser(userInfo.id)).isFalse();
     }
@@ -307,7 +293,7 @@
     @MediumTest
     @Test
     public void testRemoveUserByHandle_ThrowsException() {
-        assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null));
+        assertThrows(IllegalArgumentException.class, () -> removeUser(null));
     }
 
     @MediumTest
@@ -410,6 +396,30 @@
         assertThat(hasUser(user1.id)).isFalse();
     }
 
+    @MediumTest
+    @Test
+    public void testRemoveUserWhenPossible_withProfiles() throws Exception {
+        assumeHeadlessModeEnabled();
+        final UserInfo parentUser = createUser("Human User", /* flags= */ 0);
+        final UserInfo cloneProfileUser = createProfileForUser("Clone Profile user",
+                UserManager.USER_TYPE_PROFILE_CLONE,
+                parentUser.id);
+
+        final UserInfo workProfileUser = createProfileForUser("Work Profile user",
+                UserManager.USER_TYPE_PROFILE_MANAGED,
+                parentUser.id);
+        synchronized (mUserRemoveLock) {
+            assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
+                    /* overrideDevicePolicy= */ false))
+                    .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+            waitForUserRemovalLocked(parentUser.id);
+        }
+
+        assertThat(hasUser(parentUser.id)).isFalse();
+        assertThat(hasUser(cloneProfileUser.id)).isFalse();
+        assertThat(hasUser(workProfileUser.id)).isFalse();
+    }
+
     /** Tests creating a FULL user via specifying userType. */
     @MediumTest
     @Test
@@ -1182,6 +1192,13 @@
         }
     }
 
+    private void removeUser(UserHandle user) {
+        synchronized (mUserRemoveLock) {
+            mUserManager.removeUser(user);
+            waitForUserRemovalLocked(user.getIdentifier());
+        }
+    }
+
     private void removeUser(int userId) {
         synchronized (mUserRemoveLock) {
             mUserManager.removeUser(userId);
@@ -1261,6 +1278,12 @@
                 && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode()));
     }
 
+    private void assumeHeadlessModeEnabled() {
+        // assume headless mode is enabled
+        assumeTrue("Device doesn't have headless mode enabled",
+                UserManager.isHeadlessSystemUserMode());
+    }
+
     private boolean isAutomotive() {
         return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
     }
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a4..04ba7d3 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,13 +35,13 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
 
 import androidx.annotation.NonNull;
 
 import com.android.server.LocalServices;
 import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
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 4100ff1..3f5d331 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
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BatteryStats.CpuUsageDetails;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.MeasuredEnergyDetails;
 import android.os.Parcel;
@@ -315,8 +316,7 @@
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
-        assertThat(dump).contains("ext=E");
-        assertThat(dump).contains("Energy: A=0 B/0=100 B/1=200");
+        assertThat(dump).contains("ext=energy:A=0 B/0=100 B/1=200");
         assertThat(dump).doesNotContain("C=");
 
         String checkin = toString(item, /* checkin */ true);
@@ -325,6 +325,55 @@
         assertThat(checkin).doesNotContain("C=");
     }
 
+    @Test
+    public void cpuUsageDetails() {
+        mHistory.forceRecordAllHistory();
+        mHistory.startRecordingHistory(0, 0, /* reset */ true);
+        mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
+                1234);
+
+        CpuUsageDetails details = new CpuUsageDetails();
+        details.cpuBracketDescriptions = new String[] {"low", "Med", "HIGH"};
+        details.uid = 10123;
+        details.cpuUsageMs = new long[] { 100, 200, 300};
+        mHistory.recordCpuUsage(200, 200, details);
+
+        details.uid = 10321;
+        details.cpuUsageMs = new long[] { 400, 500, 600};
+        mHistory.recordCpuUsage(300, 300, details);
+
+        BatteryStatsHistoryIterator iterator = mHistory.iterate();
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+
+        assertThat(iterator.next(item)).isTrue();
+
+        String dump = toString(item, /* checkin */ false);
+        assertThat(dump).contains("+200ms");
+        assertThat(dump).contains("ext=cpu:u0a123: 100, 200, 300");
+        assertThat(dump).contains("ext=cpu-bracket:0:low");
+        assertThat(dump).contains("ext=cpu-bracket:1:Med");
+        assertThat(dump).contains("ext=cpu-bracket:2:HIGH");
+
+        String checkin = toString(item, /* checkin */ true);
+        assertThat(checkin).contains("XB,3,0,low");
+        assertThat(checkin).contains("XB,3,1,Med");
+        assertThat(checkin).contains("XB,3,2,HIGH");
+        assertThat(checkin).contains("XC,10123,100,200,300");
+
+        assertThat(iterator.next(item)).isTrue();
+
+        dump = toString(item, /* checkin */ false);
+        assertThat(dump).contains("+300ms");
+        assertThat(dump).contains("ext=cpu:u0a321: 400, 500, 600");
+        // Power bracket descriptions are written only once
+        assertThat(dump).doesNotContain("ext=cpu-bracket");
+
+        checkin = toString(item, /* checkin */ true);
+        assertThat(checkin).doesNotContain("XB");
+        assertThat(checkin).contains("XC,10321,400,500,600");
+    }
+
     private String toString(BatteryStats.HistoryItem item, boolean checkin) {
         BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter();
         StringWriter writer = new StringWriter();
@@ -333,4 +382,68 @@
         pw.flush();
         return writer.toString();
     }
+
+    @Test
+    public void testVarintParceler() {
+        long[] values = {
+                0,
+                1,
+                42,
+                0x1234,
+                0x10000000,
+                0x12345678,
+                0x7fffffff,
+                0xffffffffL,
+                0x100000000000L,
+                0x123456789012L,
+                0x1000000000000000L,
+                0x1234567890123456L,
+                0x7fffffffffffffffL,
+                0xffffffffffffffffL};
+
+        // Parcel subarrays of different lengths and assert the size of the resulting parcel
+        testVarintParceler(Arrays.copyOfRange(values, 0, 1), 4);   // v. 8
+        testVarintParceler(Arrays.copyOfRange(values, 0, 2), 4);   // v. 16
+        testVarintParceler(Arrays.copyOfRange(values, 0, 3), 4);   // v. 24
+        testVarintParceler(Arrays.copyOfRange(values, 0, 4), 8);   // v. 32
+        testVarintParceler(Arrays.copyOfRange(values, 0, 5), 12);  // v. 40
+        testVarintParceler(Arrays.copyOfRange(values, 0, 6), 16);  // v. 48
+        testVarintParceler(Arrays.copyOfRange(values, 0, 7), 20);  // v. 56
+        testVarintParceler(Arrays.copyOfRange(values, 0, 8), 28);  // v. 64
+        testVarintParceler(Arrays.copyOfRange(values, 0, 9), 32);  // v. 72
+        testVarintParceler(Arrays.copyOfRange(values, 0, 10), 40); // v. 80
+        testVarintParceler(Arrays.copyOfRange(values, 0, 11), 48); // v. 88
+        testVarintParceler(Arrays.copyOfRange(values, 0, 12), 60); // v. 96
+        testVarintParceler(Arrays.copyOfRange(values, 0, 13), 68); // v. 104
+        testVarintParceler(Arrays.copyOfRange(values, 0, 14), 76); // v. 112
+    }
+
+    private void testVarintParceler(long[] values, int expectedLength) {
+        BatteryStatsHistory.VarintParceler parceler = new BatteryStatsHistory.VarintParceler();
+        Parcel parcel = Parcel.obtain();
+        parcel.writeString("begin");
+        int pos = parcel.dataPosition();
+        parceler.writeLongArray(parcel, values);
+        int length = parcel.dataPosition() - pos;
+        parcel.writeString("end");
+
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(bytes, 0, bytes.length);
+        parcel.setDataPosition(0);
+
+        assertThat(parcel.readString()).isEqualTo("begin");
+
+        long[] result = new long[values.length];
+        parceler.readLongArray(parcel, result);
+
+        assertThat(result).isEqualTo(values);
+        assertThat(length).isEqualTo(expectedLength);
+
+        assertThat(parcel.readString()).isEqualTo("end");
+
+        parcel.recycle();
+    }
 }
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 5403269..c6a7fbc 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
@@ -28,7 +28,6 @@
 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.anyLong;
@@ -117,6 +116,7 @@
 
         // Initialize time-in-freq counters
         mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], new long[5]);
             mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
@@ -142,11 +142,12 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -171,6 +172,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -179,7 +181,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -200,7 +202,7 @@
         }
 
         // Validate the on-battery-screen-off counter
-        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, mMockClock.uptime * 1000,
                 mMockClock.realtime * 1000);
 
         final long[][] delta2 = {
@@ -211,6 +213,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -219,7 +222,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -253,6 +256,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         final int parentUid = testUids[1];
         final int childUid = 99099;
@@ -267,7 +271,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -302,6 +306,7 @@
         mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] testProcStates = {
@@ -316,7 +321,7 @@
             uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
             mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.elapsedRealtime());
+                    mMockClock.elapsedRealtime(), mMockClock.uptime);
         }
 
         final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
@@ -341,6 +346,7 @@
         }
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         mBatteryStatsImpl.updateCpuTimesForAllUids();
 
@@ -394,27 +400,6 @@
     }
 
     @Test
-    public void testAddCpuTimes() {
-        long[] timesA = null;
-        long[] timesB = null;
-        assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        timesA = new long[] {34, 23, 45, 24};
-        assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        timesB = timesA;
-        timesA = null;
-        assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        final long[] expected = {434, 6784, 34987, 9984};
-        timesA = new long[timesB.length];
-        for (int i = 0; i < timesA.length; ++i) {
-            timesA[i] = expected[i] - timesB[i];
-        }
-        assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-    }
-
-    @Test
     public void testMulticastWakelockAcqRel() {
         final int testUid = 10032;
         final int acquireTimeMs = 1000;
diff --git a/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
index 2b527a2..a603b93 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
@@ -19,10 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.app.IBatteryStats;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -45,20 +49,20 @@
         final Analyst analyst = new Analyst();
 
         Analyst.Report expected = new Analyst.Report();
-        expected.currentBatteryLevel = 55;
-        analyst.noteBatteryLevelChange(55);
+        expected.currentBatteryLevel = 75;
+        analyst.noteBatteryLevelChange(75);
         assertEquals(1, analyst.getReports().size());
         assertReportsEqual(expected, analyst.getReports().get(0));
 
         // Discharging
         analyst.noteBatteryLevelChange(54);
         expected.currentBatteryLevel = 54;
-        expected.cumulativeBatteryDischarge = 1;
+        expected.cumulativeBatteryDischarge = 21;
         assertEquals(1, analyst.getReports().size());
         assertReportsEqual(expected, analyst.getReports().get(0));
         analyst.noteBatteryLevelChange(50);
         expected.currentBatteryLevel = 50;
-        expected.cumulativeBatteryDischarge = 5;
+        expected.cumulativeBatteryDischarge = 25;
         assertEquals(1, analyst.getReports().size());
         assertReportsEqual(expected, analyst.getReports().get(0));
 
@@ -87,27 +91,53 @@
     }
 
     @Test
-    public void testTransaction_PeriodChange() {
-        final Analyst analyst = new Analyst();
+    public void testTransaction_PeriodChange() throws Exception {
+        IBatteryStats iBatteryStats = mock(IBatteryStats.class);
+        final Analyst analyst = new Analyst(iBatteryStats);
 
+        // Reset from enough discharge.
         Analyst.Report expected = new Analyst.Report();
-        expected.currentBatteryLevel = 55;
-        analyst.noteBatteryLevelChange(55);
+        expected.currentBatteryLevel = 75;
+        analyst.noteBatteryLevelChange(75);
 
         runTestTransactions(analyst, expected, 1);
 
         expected.currentBatteryLevel = 49;
-        expected.cumulativeBatteryDischarge = 6;
+        expected.cumulativeBatteryDischarge = 26;
         analyst.noteBatteryLevelChange(49);
 
         runTestTransactions(analyst, expected, 1);
 
         expected = new Analyst.Report();
-        expected.currentBatteryLevel = 100;
-        analyst.noteBatteryLevelChange(100);
+        expected.currentBatteryLevel = 90;
+        analyst.noteBatteryLevelChange(90);
         expected.cumulativeBatteryDischarge = 0;
 
         runTestTransactions(analyst, expected, 2);
+
+        // Reset from report being long enough.
+        doReturn(Analyst.MIN_REPORT_DURATION_FOR_RESET)
+                .when(iBatteryStats).computeBatteryScreenOffRealtimeMs();
+        expected.currentBatteryLevel = 85;
+        analyst.noteBatteryLevelChange(85);
+        expected.cumulativeBatteryDischarge = 5;
+        expected.screenOffDurationMs = Analyst.MIN_REPORT_DURATION_FOR_RESET;
+
+        runTestTransactions(analyst, expected, 2);
+
+        expected.currentBatteryLevel = 79;
+        analyst.noteBatteryLevelChange(79);
+        expected.cumulativeBatteryDischarge = 11;
+
+        runTestTransactions(analyst, expected, 2);
+
+        expected = new Analyst.Report();
+        expected.currentBatteryLevel = 80;
+        analyst.noteBatteryLevelChange(80);
+        expected.cumulativeBatteryDischarge = 0;
+        expected.screenOffDurationMs = 0;
+
+        runTestTransactions(analyst, expected, 3);
     }
 
     private void runTestTransactions(Analyst analyst, Analyst.Report lastExpectedReport,
@@ -223,6 +253,8 @@
         assertEquals(expected.numPositiveRegulations, actual.numPositiveRegulations);
         assertEquals(expected.cumulativeNegativeRegulations, actual.cumulativeNegativeRegulations);
         assertEquals(expected.numNegativeRegulations, actual.numNegativeRegulations);
+        assertEquals(expected.screenOffDurationMs, actual.screenOffDurationMs);
+        assertEquals(expected.screenOffDischargeMah, actual.screenOffDischargeMah);
     }
 
     private void assertReportListsEqual(List<Analyst.Report> expected,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
index 208f99a..a24afe6 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
@@ -79,7 +79,7 @@
             TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeCapability());
+            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
@@ -98,7 +98,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_POSSESSED,
-                    capabilities.getSuggestManualTimeCapability());
+                    capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
@@ -131,7 +131,7 @@
             TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_ALLOWED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
@@ -149,7 +149,7 @@
             TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_ALLOWED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeCapability());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
@@ -181,7 +181,7 @@
             TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
@@ -199,7 +199,7 @@
             TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeCapability());
 
             TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
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 1c6add2..7b38fa0 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -21,6 +21,8 @@
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeState;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.util.IndentingPrintWriter;
@@ -30,6 +32,8 @@
  * in tests.
  */
 class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
+    // State
+    private TimeState mTimeState;
 
     // Call tracking.
     private TelephonyTimeSuggestion mLastTelephonySuggestion;
@@ -38,9 +42,26 @@
     private NetworkTimeSuggestion mLastNetworkSuggestion;
     private GnssTimeSuggestion mLastGnssSuggestion;
     private ExternalTimeSuggestion mLastExternalSuggestion;
+    private UnixEpochTime mLastConfirmedTime;
     private boolean mDumpCalled;
 
     @Override
+    public TimeState getTimeState() {
+        return mTimeState;
+    }
+
+    @Override
+    public void setTimeState(TimeState timeState) {
+        mTimeState = timeState;
+    }
+
+    @Override
+    public boolean confirmTime(UnixEpochTime confirmationTime) {
+        mLastConfirmedTime = confirmationTime;
+        return false;
+    }
+
+    @Override
     public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) {
         mLastTelephonySuggestion = timeSuggestion;
     }
@@ -79,6 +100,7 @@
         mLastNetworkSuggestion = null;
         mLastGnssSuggestion = null;
         mLastExternalSuggestion = null;
+        mLastConfirmedTime = null;
         mDumpCalled = false;
     }
 
@@ -104,6 +126,10 @@
         assertEquals(expectedSuggestion, mLastExternalSuggestion);
     }
 
+    void verifyConfirmTimeCalled(UnixEpochTime expectedConfirmationTime) {
+        assertEquals(mLastConfirmedTime, expectedConfirmationTime);
+    }
+
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeSuggestionTest.java
index f25d94e..57d5469 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeSuggestionTest.java
@@ -21,15 +21,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.time.UnixEpochTime;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import org.junit.Test;
 
 public class GnssTimeSuggestionTest {
 
-    private static final TimestampedValue<Long> ARBITRARY_TIME =
-            new TimestampedValue<>(1111L, 2222L);
+    private static final UnixEpochTime ARBITRARY_TIME =
+            new UnixEpochTime(1111L, 2222L);
 
     @Test
     public void testEquals() {
@@ -40,9 +40,9 @@
         assertEquals(one, two);
         assertEquals(two, one);
 
-        TimestampedValue<Long> differentTime = new TimestampedValue<>(
-                ARBITRARY_TIME.getReferenceTimeMillis() + 1,
-                ARBITRARY_TIME.getValue());
+        UnixEpochTime differentTime = new UnixEpochTime(
+                ARBITRARY_TIME.getElapsedRealtimeMillis() + 1,
+                ARBITRARY_TIME.getUnixEpochTimeMillis());
         GnssTimeSuggestion three = new GnssTimeSuggestion(differentTime);
         assertNotEquals(one, three);
         assertNotEquals(three, one);
@@ -63,15 +63,15 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUnixEpochTime() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321");
+                "--elapsed_realtime 54321");
         GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test
     public void testParseCommandLineArg_validSuggestion() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345");
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
+        UnixEpochTime timeSignal = new UnixEpochTime(54321L, 12345L);
         GnssTimeSuggestion expectedSuggestion = new GnssTimeSuggestion(timeSignal);
         GnssTimeSuggestion actualSuggestion =
                 GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
@@ -81,7 +81,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_unknownArgument() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
         GnssTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
index 3f550d0..c8afb78 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
@@ -28,6 +28,7 @@
 
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
+import android.app.time.UnixEpochTime;
 import android.content.Context;
 import android.location.Location;
 import android.location.LocationListener;
@@ -35,7 +36,6 @@
 import android.location.LocationManagerInternal;
 import android.location.LocationRequest;
 import android.location.LocationTime;
-import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -73,7 +73,7 @@
 
     @Test
     public void testLocationListenerOnLocationChanged_validLocationTime_suggestsGnssTime() {
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+        UnixEpochTime timeSignal = new UnixEpochTime(
                 ELAPSED_REALTIME_MS, GNSS_TIME);
         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
         LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
@@ -178,7 +178,7 @@
     private void advanceServiceToSleepingState(
             ArgumentCaptor<LocationListener> locationListenerCaptor,
             ArgumentCaptor<OnAlarmListener> alarmListenerCaptor) {
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+        UnixEpochTime timeSignal = new UnixEpochTime(
                 ELAPSED_REALTIME_MS, GNSS_TIME);
         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
         LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeSuggestionTest.java
index f5a37b9..fcc76d3 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeSuggestionTest.java
@@ -21,15 +21,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.time.UnixEpochTime;
 import android.os.ShellCommand;
-import android.os.TimestampedValue;
 
 import org.junit.Test;
 
 public class NetworkTimeSuggestionTest {
 
-    private static final TimestampedValue<Long> ARBITRARY_TIME =
-            new TimestampedValue<>(1111L, 2222L);
+    private static final UnixEpochTime ARBITRARY_TIME =
+            new UnixEpochTime(1111L, 2222L);
     private static final int ARBITRARY_UNCERTAINTY_MILLIS = 3333;
 
     @Test
@@ -43,9 +43,9 @@
         assertEquals(one, two);
         assertEquals(two, one);
 
-        TimestampedValue<Long> differentTime = new TimestampedValue<>(
-                ARBITRARY_TIME.getReferenceTimeMillis() + 1,
-                ARBITRARY_TIME.getValue());
+        UnixEpochTime differentTime = new UnixEpochTime(
+                ARBITRARY_TIME.getElapsedRealtimeMillis() + 1,
+                ARBITRARY_TIME.getUnixEpochTimeMillis());
         NetworkTimeSuggestion three = new NetworkTimeSuggestion(
                 differentTime, ARBITRARY_UNCERTAINTY_MILLIS);
         assertNotEquals(one, three);
@@ -73,22 +73,22 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUnixEpochTime() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --uncertainty_millis 111");
+                "--elapsed_realtime 54321 --uncertainty_millis 111");
         NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_noUncertaintyMillis() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345");
         NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 
     @Test
     public void testParseCommandLineArg_validSuggestion() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345 --uncertainty_millis 111");
-        TimestampedValue<Long> timeSignal = new TimestampedValue<>(54321L, 12345L);
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --uncertainty_millis 111");
+        UnixEpochTime timeSignal = new UnixEpochTime(54321L, 12345L);
         NetworkTimeSuggestion expectedSuggestion = new NetworkTimeSuggestion(timeSignal, 111);
         NetworkTimeSuggestion actualSuggestion =
                 NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
@@ -98,7 +98,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testParseCommandLineArg_unknownArgument() {
         ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--reference_time 54321 --unix_epoch_time 12345 --bad_arg 0");
+                "--elapsed_realtime 54321 --unix_epoch_time 12345 --bad_arg 0");
         NetworkTimeSuggestion.parseCommandLineArg(testShellCommand);
     }
 }
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 f8092a6..5b61752 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
@@ -18,9 +18,9 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.app.time.UnixEpochTime;
 import android.content.Context;
 import android.os.HandlerThread;
-import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -74,7 +74,7 @@
     }
 
     private static NetworkTimeSuggestion createNetworkTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new NetworkTimeSuggestion(timeValue, 123);
     }
 
@@ -90,7 +90,7 @@
     }
 
     private static GnssTimeSuggestion createGnssTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new GnssTimeSuggestion(timeValue);
     }
 }
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 67c8c4f..f776d3d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -19,9 +19,9 @@
 import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -38,6 +38,8 @@
 import android.app.time.ExternalTimeSuggestion;
 import android.app.time.ITimeDetectorListener;
 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.app.timedetector.TimePoint;
@@ -46,7 +48,6 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelableException;
-import android.os.TimestampedValue;
 import android.util.NtpTrustedTime;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -102,8 +103,8 @@
         mMockNtpTrustedTime = mock(NtpTrustedTime.class);
 
         mTimeDetectorService = new TimeDetectorService(
-                mMockContext, mTestHandler, mFakeServiceConfigAccessor,
-                mFakeTimeDetectorStrategy, mTestCallerIdentityInjector, mMockNtpTrustedTime);
+                mMockContext, mTestHandler, mTestCallerIdentityInjector, mFakeServiceConfigAccessor,
+                mFakeTimeDetectorStrategy, mMockNtpTrustedTime);
     }
 
     @After
@@ -112,19 +113,14 @@
         mHandlerThread.join();
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetCapabilitiesAndConfig_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeDetectorService.getCapabilitiesAndConfig();
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeDetectorService::getCapabilitiesAndConfig);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -139,40 +135,30 @@
                 mTimeDetectorService.getCapabilitiesAndConfig());
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testAddListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
-        try {
-            mTimeDetectorService.addListener(mockListener);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, () -> mTimeDetectorService.addListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testRemoveListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
-        try {
-            mTimeDetectorService.removeListener(mockListener);
-            fail("Expected a SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.removeListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -191,8 +177,7 @@
             mTimeDetectorService.addListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).linkToDeath(any(), anyInt());
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -211,8 +196,7 @@
             mTestHandler.waitForMessagesToBeProcessed();
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
@@ -228,8 +212,7 @@
             mTimeDetectorService.removeListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -244,28 +227,23 @@
             mTimeDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestTelephonyTime(timeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestTelephonyTime(timeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -277,27 +255,22 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestManualTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestManualTime(manualTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -311,24 +284,20 @@
                 mTestCallerIdentityInjector.getCallingUserId(), manualTimeSuggestion);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
 
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestNetworkTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         NetworkTimeSuggestion networkTimeSuggestion = createNetworkTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
@@ -346,19 +315,16 @@
         mFakeTimeDetectorStrategy.verifySuggestNetworkTimeCalled(networkTimeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestGnssTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
@@ -376,19 +342,16 @@
         mFakeTimeDetectorStrategy.verifySuggestGnssTimeCalled(gnssTimeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestExternalTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestExternalTime(externalTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestExternalTime(externalTimeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
     }
 
     @Test
@@ -424,6 +387,106 @@
     }
 
     @Test
+    public void testGetTimeState() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+        TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+        mFakeTimeDetectorStrategy.setTimeState(fakeState);
+
+        TimeState actualState = mTimeDetectorService.getTimeState();
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        assertEquals(actualState, fakeState);
+    }
+
+    @Test
+    public void testGetTimeState_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        assertThrows(SecurityException.class, mTimeDetectorService::getTimeState);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetTimeState() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+        mTimeDetectorService.setTimeState(state);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        assertEquals(mFakeTimeDetectorStrategy.getTimeState(), state);
+    }
+
+    @Test
+    public void testSetTimeState_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
+        assertThrows(SecurityException.class, () -> mTimeDetectorService.setTimeState(state));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testConfirmTime() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        UnixEpochTime confirmationTime = new UnixEpochTime(12345L, 98765L);
+        // The fake strategy always returns false.
+        assertFalse(mTimeDetectorService.confirmTime(confirmationTime));
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        mFakeTimeDetectorStrategy.verifyConfirmTimeCalled(confirmationTime);
+    }
+
+    @Test
+    public void testConfirmTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L)));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetManualTime() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
+
+        boolean expectedResult = true; // The test strategy always returns true.
+        assertEquals(expectedResult,
+                mTimeDetectorService.setManualTime(timeSuggestion));
+
+        // The service calls "suggestManualTime()" because the logic is the same.
+        mFakeTimeDetectorStrategy.verifySuggestManualTimeCalled(
+                mTestCallerIdentityInjector.getCallingUserId(), timeSuggestion);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetManualTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
+
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.setManualTime(timeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
     public void testDump() {
         when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -441,7 +504,7 @@
                 .build();
     }
 
-    private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
+    static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
         return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionSupported(true)
@@ -456,24 +519,24 @@
 
     private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() {
         int slotIndex = 1234;
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUnixEpochTime(timeValue)
                 .build();
     }
 
     private static ManualTimeSuggestion createManualTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new ManualTimeSuggestion(timeValue);
     }
 
     private static NetworkTimeSuggestion createNetworkTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new NetworkTimeSuggestion(timeValue, 123);
     }
 
     private static GnssTimeSuggestion createGnssTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
         return new GnssTimeSuggestion(timeValue);
     }
 
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 1aea672..0863228 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.timedetector;
 
+import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
+import static com.android.server.SystemClockTime.TIME_CONFIDENCE_LOW;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_EXTERNAL;
 import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_GNSS;
 import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
@@ -29,12 +32,15 @@
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+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 androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
 import com.android.server.timezonedetector.ConfigurationChangeListener;
 
@@ -42,6 +48,7 @@
 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;
@@ -86,6 +93,7 @@
                     .setAutoDetectionSupported(true)
                     .setSystemClockUpdateThresholdMillis(
                             ARBITRARY_SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS)
+                    .setSystemClockUpdateThresholdMillis(TIME_CONFIDENCE_HIGH)
                     .setAutoSuggestionLowerBound(DEFAULT_SUGGESTION_LOWER_BOUND)
                     .setManualSuggestionLowerBound(DEFAULT_SUGGESTION_LOWER_BOUND)
                     .setSuggestionUpperBound(DEFAULT_SUGGESTION_UPPER_BOUND)
@@ -99,6 +107,7 @@
                     .setAutoDetectionSupported(true)
                     .setSystemClockUpdateThresholdMillis(
                             ARBITRARY_SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS)
+                    .setSystemClockUpdateThresholdMillis(TIME_CONFIDENCE_HIGH)
                     .setAutoSuggestionLowerBound(DEFAULT_SUGGESTION_LOWER_BOUND)
                     .setManualSuggestionLowerBound(DEFAULT_SUGGESTION_LOWER_BOUND)
                     .setSuggestionUpperBound(DEFAULT_SUGGESTION_UPPER_BOUND)
@@ -112,12 +121,14 @@
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
         mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED);
-        mFakeEnvironment.initializeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO);
+        mFakeEnvironment.initializeFakeClocks(
+                ARBITRARY_CLOCK_INITIALIZATION_INFO, TIME_CONFIDENCE_LOW);
     }
 
     @Test
     public void testSuggestTelephonyTime_autoTimeEnabled() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
         Instant testTime = ARBITRARY_TEST_TIME;
@@ -129,18 +140,21 @@
 
         long expectedSystemClockMillis =
                 script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
-        script.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
+        script.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
     }
 
     @Test
     public void testSuggestTelephonyTime_emptySuggestionIgnored() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
         TelephonyTimeSuggestion timeSuggestion =
                 script.generateTelephonyTimeSuggestion(slotIndex, null);
         script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, null);
     }
@@ -278,17 +292,115 @@
         }
     }
 
+    /**
+     * If an auto suggested time matches the current system clock, the confidence in the current
+     * system clock is raised even when auto time is disabled. The system clock itself must not be
+     * changed.
+     */
     @Test
-    public void testSuggestTelephonyTime_autoTimeDisabled() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED);
+    public void testSuggestTelephonyTime_autoTimeDisabled_suggestionMatchesSystemClock() {
+        TimestampedValue<Instant> initialClockTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        final int confidenceUpgradeThresholdMillis = 1000;
+        ConfigurationInternal configInternal =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
+                        .setSystemClockConfidenceThresholdMillis(
+                                confidenceUpgradeThresholdMillis)
+                        .build();
+        Script script = new Script()
+                .pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(configInternal);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
-        TelephonyTimeSuggestion timeSuggestion =
-                script.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
-        script.simulateTimePassing()
-                .simulateTelephonyTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
+
+        script.simulateTimePassing();
+        long timeElapsedMillis =
+                script.peekElapsedRealtimeMillis() - initialClockTime.getReferenceTimeMillis();
+
+        // Create a suggestion time that approximately matches the current system clock.
+        Instant suggestionInstant = initialClockTime.getValue()
+                .plusMillis(timeElapsedMillis)
+                .plusMillis(confidenceUpgradeThresholdMillis);
+        UnixEpochTime matchingClockTime = new UnixEpochTime(
+                script.peekElapsedRealtimeMillis(),
+                suggestionInstant.toEpochMilli());
+        TelephonyTimeSuggestion timeSuggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
+                .setUnixEpochTime(matchingClockTime)
+                .build();
+        script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    /**
+     * If an auto suggested time doesn't match the current system clock, the confidence in the
+     * current system clock will stay where it is. The system clock itself must not be changed.
+     */
+    @Test
+    public void testSuggestTelephonyTime_autoTimeDisabled_suggestionMismatchesSystemClock() {
+        TimestampedValue<Instant> initialClockTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        final int confidenceUpgradeThresholdMillis = 1000;
+        ConfigurationInternal configInternal =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
+                        .setSystemClockConfidenceThresholdMillis(
+                                confidenceUpgradeThresholdMillis)
+                        .build();
+        Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(configInternal);
+
+        int slotIndex = ARBITRARY_SLOT_INDEX;
+
+        script.simulateTimePassing();
+        long timeElapsedMillis =
+                script.peekElapsedRealtimeMillis() - initialClockTime.getReferenceTimeMillis();
+
+        // Create a suggestion time that doesn't match the current system clock closely enough.
+        Instant suggestionInstant = initialClockTime.getValue()
+                .plusMillis(timeElapsedMillis)
+                .plusMillis(confidenceUpgradeThresholdMillis + 1);
+        UnixEpochTime mismatchingClockTime = new UnixEpochTime(
+                script.peekElapsedRealtimeMillis(),
+                suggestionInstant.toEpochMilli());
+        TelephonyTimeSuggestion timeSuggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
+                .setUnixEpochTime(mismatchingClockTime)
+                .build();
+        script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    /**
+     * If a suggested time doesn't match the current system clock, the confidence in the current
+     * system clock will not drop.
+     */
+    @Test
+    public void testSuggestTelephonyTime_autoTimeDisabled_suggestionMismatchesSystemClock2() {
+        TimestampedValue<Instant> initialClockTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        final int confidenceUpgradeThresholdMillis = 1000;
+        ConfigurationInternal configInternal =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
+                        .setSystemClockConfidenceThresholdMillis(
+                                confidenceUpgradeThresholdMillis)
+                        .build();
+        Script script = new Script().pokeFakeClocks(initialClockTime, TIME_CONFIDENCE_HIGH)
+                .simulateConfigurationInternalChange(configInternal);
+
+        int slotIndex = ARBITRARY_SLOT_INDEX;
+
+        script.simulateTimePassing();
+        long timeElapsedMillis =
+                script.peekElapsedRealtimeMillis() - initialClockTime.getReferenceTimeMillis();
+
+        // Create a suggestion time that doesn't closely match the current system clock.
+        Instant initialClockInstant = initialClockTime.getValue();
+        UnixEpochTime mismatchingClockTime = new UnixEpochTime(
+                script.peekElapsedRealtimeMillis(),
+                initialClockInstant.plusMillis(timeElapsedMillis + 1_000_000).toEpochMilli());
+        TelephonyTimeSuggestion timeSuggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
+                .setUnixEpochTime(mismatchingClockTime)
+                .build();
+        script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
     @Test
@@ -298,58 +410,63 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
                         .setSystemClockUpdateThresholdMillis(systemClockUpdateThresholdMillis)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant testTime = ARBITRARY_TEST_TIME;
         int slotIndex = ARBITRARY_SLOT_INDEX;
 
         TelephonyTimeSuggestion timeSuggestion1 =
                 script.generateTelephonyTimeSuggestion(slotIndex, testTime);
-        TimestampedValue<Long> unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
+        UnixEpochTime unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
 
         // Initialize the strategy / device with a time set from a telephony suggestion.
         script.simulateTimePassing();
         long expectedSystemClockMillis1 = script.calculateTimeInMillisForNow(unixEpochTime1);
         script.simulateTelephonyTimeSuggestion(timeSuggestion1)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // The Unix epoch time increment should be larger than the system clock update threshold so
         // we know it shouldn't be ignored for other reasons.
-        long validUnixEpochTimeMillis = unixEpochTime1.getValue()
+        long validUnixEpochTimeMillis = unixEpochTime1.getUnixEpochTimeMillis()
                 + (2 * systemClockUpdateThresholdMillis);
 
-        // Now supply a new signal that has an obviously bogus reference time : older than the last
-        // one.
-        long referenceTimeBeforeLastSignalMillis = unixEpochTime1.getReferenceTimeMillis() - 1;
-        TimestampedValue<Long> unixEpochTime2 = new TimestampedValue<>(
+        // Now supply a new signal that has an obviously bogus elapsed realtime : older than the
+        // last one.
+        long referenceTimeBeforeLastSignalMillis = unixEpochTime1.getElapsedRealtimeMillis() - 1;
+        UnixEpochTime unixEpochTime2 = new UnixEpochTime(
                 referenceTimeBeforeLastSignalMillis, validUnixEpochTimeMillis);
         TelephonyTimeSuggestion timeSuggestion2 =
                 createTelephonyTimeSuggestion(slotIndex, unixEpochTime2);
         script.simulateTelephonyTimeSuggestion(timeSuggestion2)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
-        // Now supply a new signal that has an obviously bogus reference time : substantially in the
-        // future.
+        // Now supply a new signal that has an obviously bogus elapsed realtime; one substantially
+        // in the future.
         long referenceTimeInFutureMillis =
-                unixEpochTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
-        TimestampedValue<Long> unixEpochTime3 = new TimestampedValue<>(
+                unixEpochTime1.getElapsedRealtimeMillis() + Integer.MAX_VALUE + 1;
+        UnixEpochTime unixEpochTime3 = new UnixEpochTime(
                 referenceTimeInFutureMillis, validUnixEpochTimeMillis);
         TelephonyTimeSuggestion timeSuggestion3 =
                 createTelephonyTimeSuggestion(slotIndex, unixEpochTime3);
         script.simulateTelephonyTimeSuggestion(timeSuggestion3)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Just to prove validUnixEpochTimeMillis is valid.
-        long validReferenceTimeMillis = unixEpochTime1.getReferenceTimeMillis() + 100;
-        TimestampedValue<Long> unixEpochTime4 = new TimestampedValue<>(
+        long validReferenceTimeMillis = unixEpochTime1.getElapsedRealtimeMillis() + 100;
+        UnixEpochTime unixEpochTime4 = new UnixEpochTime(
                 validReferenceTimeMillis, validUnixEpochTimeMillis);
         long expectedSystemClockMillis4 = script.calculateTimeInMillisForNow(unixEpochTime4);
         TelephonyTimeSuggestion timeSuggestion4 =
                 createTelephonyTimeSuggestion(slotIndex, unixEpochTime4);
         script.simulateTelephonyTimeSuggestion(timeSuggestion4)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion4);
     }
@@ -362,13 +479,14 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
                         .setSystemClockUpdateThresholdMillis(systemClockUpdateThresholdMillis)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
         Instant testTime = ARBITRARY_TEST_TIME;
         TelephonyTimeSuggestion timeSuggestion1 =
                 script.generateTelephonyTimeSuggestion(slotIndex, testTime);
-        TimestampedValue<Long> unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
+        UnixEpochTime unixEpochTime1 = timeSuggestion1.getUnixEpochTime();
 
         // Simulate time passing.
         script.simulateTimePassing(clockIncrementMillis);
@@ -376,6 +494,7 @@
         // Simulate the time signal being received. It should not be used because auto time
         // detection is off but it should be recorded.
         script.simulateTelephonyTimeSuggestion(timeSuggestion1)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
@@ -386,11 +505,13 @@
 
         // Turn on auto time detection.
         script.simulateAutoTimeDetectionToggle()
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Turn off auto time detection.
         script.simulateAutoTimeDetectionToggle()
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
@@ -408,18 +529,21 @@
         // The new time, though valid, should not be set in the system clock because auto time is
         // disabled.
         script.simulateTelephonyTimeSuggestion(timeSuggestion2)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
 
         // Turn on auto time detection.
         script.simulateAutoTimeDetectionToggle()
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
                 .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
     }
 
     @Test
     public void testSuggestTelephonyTime_maxSuggestionAge() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
         Instant testTime = ARBITRARY_TEST_TIME;
@@ -431,6 +555,7 @@
         long expectedSystemClockMillis =
                 script.calculateTimeInMillisForNow(telephonySuggestion.getUnixEpochTime());
         script.simulateTelephonyTimeSuggestion(telephonySuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(
                         expectedSystemClockMillis  /* expectedNetworkBroadcast */)
                 .assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
@@ -442,7 +567,7 @@
         script.simulateTimePassing(TimeDetectorStrategyImpl.MAX_SUGGESTION_TIME_AGE_MILLIS);
 
         // Look inside and check what the strategy considers the current best telephony suggestion.
-        // It should still be the, it's just no longer used.
+        // It should still be there, it's just no longer used.
         assertNull(script.peekBestTelephonySuggestion());
         script.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
     }
@@ -454,12 +579,14 @@
                         .setOriginPriorities(ORIGIN_TELEPHONY)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
         TelephonyTimeSuggestion timeSuggestion =
                 script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, belowLowerBound);
         script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -470,12 +597,14 @@
                         .setOriginPriorities(ORIGIN_TELEPHONY)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
         TelephonyTimeSuggestion timeSuggestion =
                 script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, aboveLowerBound);
         script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
     }
 
@@ -486,12 +615,14 @@
                         .setOriginPriorities(ORIGIN_TELEPHONY)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
         TelephonyTimeSuggestion timeSuggestion =
                 script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, aboveUpperBound);
         script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -502,18 +633,21 @@
                         .setOriginPriorities(ORIGIN_TELEPHONY)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
         TelephonyTimeSuggestion timeSuggestion =
                 script.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, belowUpperBound);
         script.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
     }
 
     @Test
     public void testSuggestManualTime_autoTimeDisabled() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         ManualTimeSuggestion timeSuggestion =
                 script.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -524,12 +658,14 @@
                 script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
         script.simulateManualTimeSuggestion(
                 ARBITRARY_USER_ID, timeSuggestion, true /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
     public void testSuggestManualTime_retainsAutoSignal() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
 
@@ -544,6 +680,7 @@
         long expectedAutoClockMillis =
                 script.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime());
         script.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
@@ -552,6 +689,7 @@
 
         // Switch to manual.
         script.simulateAutoTimeDetectionToggle()
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
@@ -568,6 +706,7 @@
                 script.calculateTimeInMillisForNow(manualTimeSuggestion.getUnixEpochTime());
         script.simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, manualTimeSuggestion, true /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
@@ -580,17 +719,20 @@
         expectedAutoClockMillis =
                 script.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUnixEpochTime());
         script.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
         // Switch back to manual - nothing should happen to the clock.
         script.simulateAutoTimeDetectionToggle()
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasNotSetAndResetCallTracking()
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
     }
 
     @Test
     public void testSuggestManualTime_isIgnored_whenAutoTimeEnabled() {
-        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED);
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                        .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         ManualTimeSuggestion timeSuggestion =
                 script.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -598,6 +740,7 @@
         script.simulateTimePassing()
                 .simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, timeSuggestion, false /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -607,12 +750,14 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
         ManualTimeSuggestion timeSuggestion = script.generateManualTimeSuggestion(aboveUpperBound);
         script.simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, timeSuggestion, false /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -622,12 +767,14 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
         ManualTimeSuggestion timeSuggestion = script.generateManualTimeSuggestion(belowUpperBound);
         script.simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, timeSuggestion, true /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
     }
 
@@ -637,12 +784,14 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
                         .setManualSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
         ManualTimeSuggestion timeSuggestion = script.generateManualTimeSuggestion(belowLowerBound);
         script.simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, timeSuggestion, false /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -652,12 +801,14 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED)
                         .setManualSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
         ManualTimeSuggestion timeSuggestion = script.generateManualTimeSuggestion(aboveLowerBound);
         script.simulateManualTimeSuggestion(
                         ARBITRARY_USER_ID, timeSuggestion, true /* expectedResult */)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
     }
 
@@ -667,7 +818,8 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
                         .setOriginPriorities(ORIGIN_NETWORK)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         NetworkTimeSuggestion timeSuggestion =
                 script.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -677,6 +829,7 @@
         long expectedSystemClockMillis =
                 script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
         script.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
@@ -703,12 +856,14 @@
                         .setOriginPriorities(ORIGIN_NETWORK)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
         NetworkTimeSuggestion timeSuggestion =
                 script.generateNetworkTimeSuggestion(belowLowerBound);
         script.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -719,12 +874,14 @@
                         .setOriginPriorities(ORIGIN_NETWORK)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
         NetworkTimeSuggestion timeSuggestion =
                 script.generateNetworkTimeSuggestion(aboveLowerBound);
         script.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
     }
 
@@ -735,12 +892,14 @@
                         .setOriginPriorities(ORIGIN_NETWORK)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
         NetworkTimeSuggestion timeSuggestion =
                 script.generateNetworkTimeSuggestion(aboveUpperBound);
         script.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -751,12 +910,14 @@
                         .setOriginPriorities(ORIGIN_NETWORK)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
         NetworkTimeSuggestion timeSuggestion =
                 script.generateNetworkTimeSuggestion(belowUpperBound);
         script.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
     }
 
@@ -766,7 +927,8 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
                         .setOriginPriorities(ORIGIN_GNSS)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         GnssTimeSuggestion timeSuggestion =
                 script.generateGnssTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -776,6 +938,7 @@
         long expectedSystemClockMillis =
                 script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
         script.simulateGnssTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
@@ -802,12 +965,14 @@
                         .setOriginPriorities(ORIGIN_GNSS)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
         GnssTimeSuggestion timeSuggestion =
                 script.generateGnssTimeSuggestion(belowLowerBound);
         script.simulateGnssTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -818,12 +983,14 @@
                         .setOriginPriorities(ORIGIN_GNSS)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
         GnssTimeSuggestion timeSuggestion =
                 script.generateGnssTimeSuggestion(aboveLowerBound);
         script.simulateGnssTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
     }
 
@@ -834,12 +1001,14 @@
                         .setOriginPriorities(ORIGIN_GNSS)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
         GnssTimeSuggestion timeSuggestion =
                 script.generateGnssTimeSuggestion(aboveUpperBound);
         script.simulateGnssTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -850,12 +1019,14 @@
                         .setOriginPriorities(ORIGIN_GNSS)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
         GnssTimeSuggestion timeSuggestion =
                 script.generateGnssTimeSuggestion(belowUpperBound);
         script.simulateGnssTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
     }
 
@@ -865,7 +1036,8 @@
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
                         .setOriginPriorities(ORIGIN_EXTERNAL)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         ExternalTimeSuggestion timeSuggestion =
                 script.generateExternalTimeSuggestion(ARBITRARY_TEST_TIME);
@@ -875,6 +1047,7 @@
         long expectedSystemClockMillis =
                 script.calculateTimeInMillisForNow(timeSuggestion.getUnixEpochTime());
         script.simulateExternalTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
@@ -901,12 +1074,14 @@
                         .setOriginPriorities(ORIGIN_EXTERNAL)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowLowerBound = TEST_SUGGESTION_LOWER_BOUND.minusSeconds(1);
         ExternalTimeSuggestion timeSuggestion =
                 script.generateExternalTimeSuggestion(belowLowerBound);
         script.simulateExternalTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -917,12 +1092,14 @@
                         .setOriginPriorities(ORIGIN_EXTERNAL)
                         .setAutoSuggestionLowerBound(TEST_SUGGESTION_LOWER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveLowerBound = TEST_SUGGESTION_LOWER_BOUND.plusSeconds(1);
         ExternalTimeSuggestion timeSuggestion =
                 script.generateExternalTimeSuggestion(aboveLowerBound);
         script.simulateExternalTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(aboveLowerBound.toEpochMilli());
     }
 
@@ -933,12 +1110,14 @@
                         .setOriginPriorities(ORIGIN_EXTERNAL)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant aboveUpperBound = TEST_SUGGESTION_UPPER_BOUND.plusSeconds(1);
         ExternalTimeSuggestion timeSuggestion =
                 script.generateExternalTimeSuggestion(aboveUpperBound);
         script.simulateExternalTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
@@ -949,16 +1128,138 @@
                         .setOriginPriorities(ORIGIN_EXTERNAL)
                         .setSuggestionUpperBound(TEST_SUGGESTION_UPPER_BOUND)
                         .build();
-        Script script = new Script().simulateConfigurationInternalChange(configInternal);
+        Script script = new Script().simulateConfigurationInternalChange(configInternal)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
 
         Instant belowUpperBound = TEST_SUGGESTION_UPPER_BOUND.minusSeconds(1);
         ExternalTimeSuggestion timeSuggestion =
                 script.generateExternalTimeSuggestion(belowUpperBound);
         script.simulateExternalTimeSuggestion(timeSuggestion)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
                 .verifySystemClockWasSetAndResetCallTracking(belowUpperBound.toEpochMilli());
     }
 
     @Test
+    public void testGetTimeState() {
+        TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+        UnixEpochTime systemClockTime = new UnixEpochTime(deviceTime.getReferenceTimeMillis(),
+                deviceTime.getValue().toEpochMilli());
+
+        // When confidence is low, the user should confirm.
+        script.assertGetTimeStateReturns(new TimeState(systemClockTime, true));
+
+        // When confidence is high, no need for the user to confirm.
+        script.pokeFakeClocks(deviceTime, TIME_ZONE_CONFIDENCE_HIGH)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH);
+
+        script.assertGetTimeStateReturns(new TimeState(systemClockTime, false));
+    }
+
+    @Test
+    public void testSetTimeState() {
+        TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+
+        UnixEpochTime systemClockTime = new UnixEpochTime(11111L, 222222L);
+        boolean userShouldConfirmTime = false;
+        TimeState state = new TimeState(systemClockTime, userShouldConfirmTime);
+        script.simulateSetTimeState(state);
+
+        UnixEpochTime expectedTime = systemClockTime.at(script.peekElapsedRealtimeMillis());
+        long expectedTimeMillis = expectedTime.getUnixEpochTimeMillis();
+        // userShouldConfirmTime == high confidence
+        script.verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasSetAndResetCallTracking(expectedTimeMillis);
+
+        TimeState expectedTimeState = new TimeState(expectedTime, userShouldConfirmTime);
+        script.assertGetTimeStateReturns(expectedTimeState);
+    }
+
+    @Test
+    public void testConfirmTime_autoDisabled() {
+        testConfirmTime(CONFIG_AUTO_ENABLED);
+    }
+
+    @Test
+    public void testConfirmTime_autoEnabled() {
+        testConfirmTime(CONFIG_AUTO_ENABLED);
+    }
+
+    private void testConfirmTime(ConfigurationInternal config) {
+        TimestampedValue<Instant> deviceTime = ARBITRARY_CLOCK_INITIALIZATION_INFO;
+        Script script = new Script().simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED)
+                .pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW);
+
+        long maxConfidenceThreshold = config.getSystemClockConfidenceThresholdMillis();
+        UnixEpochTime incorrectTime1 =
+                new UnixEpochTime(
+                        deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold + 1,
+                        deviceTime.getValue().toEpochMilli());
+        script.simulateConfirmTime(incorrectTime1, false)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        UnixEpochTime incorrectTime2 =
+                new UnixEpochTime(
+                        deviceTime.getReferenceTimeMillis(),
+                        deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold + 1);
+        script.simulateConfirmTime(incorrectTime2, false)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Confirm using a time that is at the threshold.
+        UnixEpochTime correctTime1 =
+                new UnixEpochTime(
+                        deviceTime.getReferenceTimeMillis(),
+                        deviceTime.getValue().toEpochMilli() + maxConfidenceThreshold);
+        script.simulateConfirmTime(correctTime1, true)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Reset back to low confidence.
+        script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Confirm using a time that is at the threshold.
+        UnixEpochTime correctTime2 =
+                new UnixEpochTime(
+                        deviceTime.getReferenceTimeMillis() + maxConfidenceThreshold,
+                        deviceTime.getValue().toEpochMilli());
+        script.simulateConfirmTime(correctTime2, true)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Reset back to low confidence.
+        script.pokeFakeClocks(deviceTime, TIME_CONFIDENCE_LOW)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_LOW)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Confirm using a time that exactly matches.
+        UnixEpochTime correctTime3 =
+                new UnixEpochTime(
+                        deviceTime.getReferenceTimeMillis(),
+                        deviceTime.getValue().toEpochMilli());
+        script.simulateConfirmTime(correctTime3, true)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Now try to confirm using another incorrect time: Confidence should remain high as the
+        // confirmation is ignored / returns false.
+        script.simulateConfirmTime(incorrectTime1, false)
+                .verifySystemClockConfidence(TIME_CONFIDENCE_HIGH)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    @Test
     public void highPrioritySuggestionsBeatLowerPrioritySuggestions_telephonyNetworkOrigins() {
         ConfigurationInternal configInternal =
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED)
@@ -1472,6 +1773,7 @@
         private boolean mWakeLockAcquired;
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
+        private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
         private ConfigurationChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
@@ -1481,9 +1783,10 @@
             mConfigurationInternal = configurationInternal;
         }
 
-        public void initializeFakeClocks(TimestampedValue<Instant> timeInfo) {
+        public void initializeFakeClocks(
+                TimestampedValue<Instant> timeInfo, @TimeConfidence int timeConfidence) {
             pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
-            pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
+            pokeSystemClockMillis(timeInfo.getValue().toEpochMilli(), timeConfidence);
         }
 
         @Override
@@ -1515,10 +1818,23 @@
         }
 
         @Override
-        public void setSystemClock(long newTimeMillis) {
+        public @TimeConfidence int systemClockConfidence() {
+            return mSystemClockConfidence;
+        }
+
+        @Override
+        public void setSystemClock(
+                long newTimeMillis, @TimeConfidence int confidence, String logMsg) {
             assertWakeLockAcquired();
             mSystemClockWasSet = true;
             mSystemClockMillis = newTimeMillis;
+            mSystemClockConfidence = confidence;
+        }
+
+        @Override
+        public void setSystemClockConfidence(@TimeConfidence int confidence, String logMsg) {
+            assertWakeLockAcquired();
+            mSystemClockConfidence = confidence;
         }
 
         @Override
@@ -1527,6 +1843,16 @@
             mWakeLockAcquired = false;
         }
 
+        @Override
+        public void addDebugLogEntry(String logMsg) {
+            // No-op for tests
+        }
+
+        @Override
+        public void dumpDebugLog(PrintWriter printWriter) {
+            // No-op for tests
+        }
+
         // Methods below are for managing the fake's behavior.
 
         void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1538,8 +1864,9 @@
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
 
-        void pokeSystemClockMillis(long systemClockMillis) {
+        void pokeSystemClockMillis(long systemClockMillis, @TimeConfidence int timeConfidence) {
             mSystemClockMillis = systemClockMillis;
+            mSystemClockConfidence = timeConfidence;
         }
 
         long peekElapsedRealtimeMillis() {
@@ -1567,6 +1894,10 @@
             assertEquals(expectedSystemClockMillis, mSystemClockMillis);
         }
 
+        public void verifySystemClockConfidenceLatest(@TimeConfidence int expectedConfidence) {
+            assertEquals(expectedConfidence, mSystemClockConfidence);
+        }
+
         void resetCallTracking() {
             mSystemClockWasSet = false;
         }
@@ -1589,6 +1920,14 @@
             mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
         }
 
+        Script pokeFakeClocks(TimestampedValue<Instant> initialClockTime,
+                @TimeConfidence int timeConfidence) {
+            mFakeEnvironment.pokeElapsedRealtimeMillis(initialClockTime.getReferenceTimeMillis());
+            mFakeEnvironment.pokeSystemClockMillis(
+                    initialClockTime.getValue().toEpochMilli(), timeConfidence);
+            return this;
+        }
+
         long peekElapsedRealtimeMillis() {
             return mFakeEnvironment.peekElapsedRealtimeMillis();
         }
@@ -1663,6 +2002,12 @@
             return simulateTimePassing(999);
         }
 
+        /** Calls {@link TimeDetectorStrategy#setTimeState(TimeState)}. */
+        Script simulateSetTimeState(TimeState timeState) {
+            mTimeDetectorStrategy.setTimeState(timeState);
+            return this;
+        }
+
         Script verifySystemClockWasNotSetAndResetCallTracking() {
             mFakeEnvironment.verifySystemClockNotSet();
             mFakeEnvironment.resetCallTracking();
@@ -1675,6 +2020,11 @@
             return this;
         }
 
+        Script verifySystemClockConfidence(@TimeConfidence int expectedConfidence) {
+            mFakeEnvironment.verifySystemClockConfidenceLatest(expectedConfidence);
+            return this;
+        }
+
         /**
          * White box test info: Asserts the latest suggestion for the slotIndex is as expected.
          */
@@ -1710,6 +2060,11 @@
             return this;
         }
 
+        Script assertGetTimeStateReturns(TimeState expected) {
+            assertEquals(expected, mTimeDetectorStrategy.getTimeState());
+            return this;
+        }
+
         /**
          * White box test info: Returns the telephony suggestion that would be used, if any, given
          * the current elapsed real time clock and regardless of origin prioritization.
@@ -1744,11 +2099,11 @@
 
         /**
          * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
-         * reference time.
+         * elapsed realtime.
          */
         ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
-            TimestampedValue<Long> unixEpochTime =
-                    new TimestampedValue<>(
+            UnixEpochTime unixEpochTime =
+                    new UnixEpochTime(
                             mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new ManualTimeSuggestion(unixEpochTime);
@@ -1756,17 +2111,16 @@
 
         /**
          * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
-         * the reference time.
+         * the elapsed realtime.
          */
         TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, long timeMillis) {
-            TimestampedValue<Long> time =
-                    new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
+            UnixEpochTime time = new UnixEpochTime(peekElapsedRealtimeMillis(), timeMillis);
             return createTelephonyTimeSuggestion(slotIndex, time);
         }
 
         /**
          * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
-         * the reference time.
+         * the elapsed realtime.
          */
         TelephonyTimeSuggestion generateTelephonyTimeSuggestion(
                 int slotIndex, Instant suggestedTime) {
@@ -1778,11 +2132,11 @@
 
         /**
          * Generates a NetworkTimeSuggestion using the current elapsed realtime clock for the
-         * reference time.
+         * elapsed realtime.
          */
         NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
-            TimestampedValue<Long> unixEpochTime =
-                    new TimestampedValue<>(
+            UnixEpochTime unixEpochTime =
+                    new UnixEpochTime(
                             mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new NetworkTimeSuggestion(unixEpochTime, 123);
@@ -1790,11 +2144,11 @@
 
         /**
          * Generates a GnssTimeSuggestion using the current elapsed realtime clock for the
-         * reference time.
+         * elapsed realtime.
          */
         GnssTimeSuggestion generateGnssTimeSuggestion(Instant suggestedTime) {
-            TimestampedValue<Long> unixEpochTime =
-                    new TimestampedValue<>(
+            UnixEpochTime unixEpochTime =
+                    new UnixEpochTime(
                             mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new GnssTimeSuggestion(unixEpochTime);
@@ -1802,7 +2156,7 @@
 
         /**
          * Generates a ExternalTimeSuggestion using the current elapsed realtime clock for the
-         * reference time.
+         * elapsed realtime.
          */
         ExternalTimeSuggestion generateExternalTimeSuggestion(Instant suggestedTime) {
             return new ExternalTimeSuggestion(mFakeEnvironment.peekElapsedRealtimeMillis(),
@@ -1813,13 +2167,18 @@
          * Calculates what the supplied time would be when adjusted for the movement of the fake
          * elapsed realtime clock.
          */
-        long calculateTimeInMillisForNow(TimestampedValue<Long> unixEpochTime) {
-            return TimeDetectorStrategy.getTimeAt(unixEpochTime, peekElapsedRealtimeMillis());
+        long calculateTimeInMillisForNow(UnixEpochTime unixEpochTime) {
+            return unixEpochTime.at(peekElapsedRealtimeMillis()).getUnixEpochTimeMillis();
+        }
+
+        Script simulateConfirmTime(UnixEpochTime confirmationTime, boolean expectedReturnValue) {
+            assertEquals(expectedReturnValue, mTimeDetectorStrategy.confirmTime(confirmationTime));
+            return this;
         }
     }
 
     private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,
-            TimestampedValue<Long> unixEpochTime) {
+            UnixEpochTime unixEpochTime) {
         return new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUnixEpochTime(unixEpochTime)
                 .build();
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
deleted file mode 100644
index f1e9191..0000000
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
+++ /dev/null
@@ -1,47 +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.timedetector;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.TimestampedValue;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class TimeDetectorStrategyTest {
-
-    @Test
-    public void testGetTimeAt() {
-        long timeMillis = 1000L;
-        int referenceTimeMillis = 100;
-        TimestampedValue<Long> timestampedValue =
-                new TimestampedValue<>(referenceTimeMillis, timeMillis);
-        // Reference time is after the timestamp.
-        assertEquals(
-                timeMillis + (125 - referenceTimeMillis),
-                TimeDetectorStrategy.getTimeAt(timestampedValue, 125));
-
-        // Reference time is before the timestamp.
-        assertEquals(
-                timeMillis + (75 - referenceTimeMillis),
-                TimeDetectorStrategy.getTimeAt(timestampedValue, 75));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 767c466..810bd82 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -76,7 +76,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_NOT_APPLICABLE,
-                    capabilities.getSuggestManualTimeZoneCapability());
+                    capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
@@ -102,7 +102,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_POSSESSED,
-                    capabilities.getSuggestManualTimeZoneCapability());
+                    capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
@@ -143,7 +143,7 @@
             assertEquals(CAPABILITY_NOT_ALLOWED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_NOT_ALLOWED,
-                    capabilities.getSuggestManualTimeZoneCapability());
+                    capabilities.getSetManualTimeZoneCapability());
             // This has user privacy implications so it is not restricted in the same way as others.
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -170,7 +170,7 @@
             assertEquals(CAPABILITY_NOT_ALLOWED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_NOT_ALLOWED,
-                    capabilities.getSuggestManualTimeZoneCapability());
+                    capabilities.getSetManualTimeZoneCapability());
             // This has user privacy implications so it is not restricted in the same way as others.
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
@@ -211,7 +211,7 @@
             TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
@@ -235,7 +235,7 @@
             TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
@@ -279,7 +279,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             assertEquals(CAPABILITY_NOT_APPLICABLE,
-                    capabilities.getSuggestManualTimeZoneCapability());
+                    capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
@@ -303,7 +303,7 @@
             TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
-            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index c9fc033..339e5b2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -20,19 +20,40 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
 class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
+    private TimeZoneState mTimeZoneState;
+
     // Call tracking.
     private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
     private ManualTimeZoneSuggestion mLastManualSuggestion;
+    private Integer mLastManualSuggestionUserId;
     private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
+    private String mLastConfirmedTimeZone;
     private boolean mDumpCalled;
 
     @Override
+    public boolean confirmTimeZone(String timeZoneId) {
+        mLastConfirmedTimeZone = timeZoneId;
+        return false;
+    }
+
+    @Override
+    public TimeZoneState getTimeZoneState() {
+        return mTimeZoneState;
+    }
+
+    @Override
+    public void setTimeZoneState(TimeZoneState timeZoneState) {
+        mTimeZoneState = timeZoneState;
+    }
+
+    @Override
     public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         mLastGeolocationSuggestion = timeZoneSuggestion;
     }
@@ -40,6 +61,7 @@
     @Override
     public boolean suggestManualTimeZone(
             @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
+        mLastManualSuggestionUserId = userId;
         mLastManualSuggestion = timeZoneSuggestion;
         return true;
     }
@@ -78,8 +100,10 @@
     void resetCallTracking() {
         mLastGeolocationSuggestion = null;
         mLastManualSuggestion = null;
+        mLastManualSuggestionUserId = null;
         mLastTelephonySuggestion = null;
         mDumpCalled = false;
+        mLastConfirmedTimeZone = null;
     }
 
     void verifySuggestGeolocationTimeZoneCalled(
@@ -87,7 +111,9 @@
         assertEquals(expectedSuggestion, mLastGeolocationSuggestion);
     }
 
-    void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
+    void verifySuggestManualTimeZoneCalled(
+            @UserIdInt int expectedUserId, ManualTimeZoneSuggestion expectedSuggestion) {
+        assertEquals((Integer) expectedUserId, mLastManualSuggestionUserId);
         assertEquals(expectedSuggestion, mLastManualSuggestion);
     }
 
@@ -98,4 +124,8 @@
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
+
+    void verifyConfirmTimeZoneCalled(String expectedTimeZoneId) {
+        assertEquals(expectedTimeZoneId, mLastConfirmedTimeZone);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 6365b98..fc64597 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -17,7 +17,8 @@
 package com.android.server.timezonedetector;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -33,6 +34,7 @@
 
 import android.app.time.ITimeZoneDetectorListener;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.Context;
@@ -95,19 +97,14 @@
         mHandlerThread.join();
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetCapabilitiesAndConfig_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeZoneDetectorService.getCapabilitiesAndConfig();
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeZoneDetectorService::getCapabilitiesAndConfig);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -122,40 +119,31 @@
                 mTimeZoneDetectorService.getCapabilitiesAndConfig());
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testAddListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
-        try {
-            mTimeZoneDetectorService.addListener(mockListener);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.addListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testRemoveListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
-        try {
-            mTimeZoneDetectorService.removeListener(mockListener);
-            fail("Expected a SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.removeListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -174,8 +162,7 @@
             mTimeZoneDetectorService.addListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).linkToDeath(any(), anyInt());
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -194,8 +181,7 @@
             mTestHandler.waitForMessagesToBeProcessed();
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
@@ -211,8 +197,7 @@
             mTimeZoneDetectorService.removeListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -227,28 +212,23 @@
             mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestGeolocationTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
     }
 
     @Test
@@ -261,27 +241,22 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SET_TIME_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestManualTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -294,43 +269,35 @@
         assertEquals(expectedResult,
                 mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion));
 
-        mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
+        mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
+                mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -342,14 +309,113 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion);
     }
 
     @Test
+    public void testGetTimeZoneState() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+        TimeZoneState fakeState = new TimeZoneState("Europe/Narnia", true);
+        mFakeTimeZoneDetectorStrategy.setTimeZoneState(fakeState);
+
+        TimeZoneState actualState = mTimeZoneDetectorService.getTimeZoneState();
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        assertEquals(actualState, fakeState);
+    }
+
+    @Test
+    public void testGetTimeZoneState_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        assertThrows(SecurityException.class, mTimeZoneDetectorService::getTimeZoneState);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetTimeZoneState() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
+        mTimeZoneDetectorService.setTimeZoneState(state);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        assertEquals(mFakeTimeZoneDetectorStrategy.getTimeZoneState(), state);
+    }
+
+    @Test
+    public void testSetTimeZoneState_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.setTimeZoneState(state));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testConfirmTimeZone() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        // The fake strategy always returns false.
+        assertFalse(mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+        mFakeTimeZoneDetectorStrategy.verifyConfirmTimeZoneCalled("Europe/Narnia");
+    }
+
+    @Test
+    public void testConfirmTimeZone_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetManualTimeZone() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+        boolean expectedResult = true; // The test strategy always returns true.
+        assertEquals(expectedResult,
+                mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion));
+
+        // The service calls "suggestManualTimeZone()" because the logic is the same.
+        mFakeTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(
+                mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
+    public void testSetManualTimeZone_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
+    }
+
+    @Test
     public void testDump() {
         when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 23a9013..77d8b8b 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -24,6 +24,8 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
@@ -32,6 +34,7 @@
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD;
 
 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;
@@ -39,16 +42,19 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
 
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -180,7 +186,7 @@
         TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
                 createEmptySlotIndex2Suggestion();
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
 
@@ -293,7 +299,7 @@
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
             // Start with the device in a known state.
-            script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+            script.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                     .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
                     .resetConfigurationTracking();
 
@@ -345,7 +351,7 @@
     @Test
     public void testTelephonySuggestionsSingleSlotId() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
 
@@ -411,7 +417,7 @@
                         TELEPHONY_SCORE_NONE);
 
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_DISABLED)
                 .resetConfigurationTracking()
 
@@ -550,7 +556,7 @@
                         .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                         .build();
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(geoTzEnabledConfig)
                 .resetConfigurationTracking();
 
@@ -565,7 +571,7 @@
     @Test
     public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_ENABLED)
                 .resetConfigurationTracking();
 
@@ -580,7 +586,7 @@
     @Test
     public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
 
@@ -596,7 +602,7 @@
     @Test
     public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_USER_RESTRICTED_AUTO_DISABLED)
                 .resetConfigurationTracking();
 
@@ -612,7 +618,7 @@
     @Test
     public void testManualSuggestion_autoDetectNotSupported() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DETECT_NOT_SUPPORTED)
                 .resetConfigurationTracking();
 
@@ -628,7 +634,7 @@
     @Test
     public void testGeoSuggestion_uncertain() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .resetConfigurationTracking();
 
@@ -645,7 +651,7 @@
     @Test
     public void testGeoSuggestion_noZones() {
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .resetConfigurationTracking();
 
@@ -664,7 +670,7 @@
                 createCertainGeolocationSuggestion("Europe/London");
 
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .resetConfigurationTracking();
 
@@ -690,7 +696,7 @@
                 createCertainGeolocationSuggestion("Europe/Paris");
 
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .resetConfigurationTracking();
 
@@ -731,7 +737,7 @@
                 "Europe/Paris");
 
         Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
 
@@ -777,7 +783,7 @@
 
         Script script = new Script()
                 .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
 
@@ -911,7 +917,7 @@
 
         Script script = new Script()
                 .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
 
@@ -967,6 +973,78 @@
     }
 
     @Test
+    public void testGetTimeZoneState() {
+        Script script = new Script()
+                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+                .resetConfigurationTracking();
+
+        String timeZoneId = "Europe/London";
+
+        // When confidence is low, the user should confirm.
+        script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW);
+        assertEquals(new TimeZoneState(timeZoneId, true),
+                mTimeZoneDetectorStrategy.getTimeZoneState());
+
+        // When confidence is high, no need for the user to confirm.
+        script.initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+
+        assertEquals(new TimeZoneState(timeZoneId, false),
+                mTimeZoneDetectorStrategy.getTimeZoneState());
+    }
+
+    @Test
+    public void testSetTimeZoneState() {
+        Script script = new Script()
+                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+                .resetConfigurationTracking();
+
+        String timeZoneId = "Europe/London";
+        boolean userShouldConfirmId = false;
+        TimeZoneState state = new TimeZoneState(timeZoneId, userShouldConfirmId);
+        mTimeZoneDetectorStrategy.setTimeZoneState(state);
+
+        script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+        assertEquals(state, mTimeZoneDetectorStrategy.getTimeZoneState());
+    }
+
+    @Test
+    public void testConfirmTimeZone_autoDisabled() {
+        testConfirmTimeZone(CONFIG_AUTO_DISABLED_GEO_DISABLED);
+    }
+
+    @Test
+    public void testConfirmTimeZone_autoEnabled() {
+        testConfirmTimeZone(CONFIG_AUTO_ENABLED_GEO_DISABLED);
+    }
+
+    private void testConfirmTimeZone(ConfigurationInternal config) {
+        String timeZoneId = "Europe/London";
+        Script script = new Script()
+                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(config)
+                .resetConfigurationTracking();
+
+        String incorrectTimeZoneId = "Europe/Paris";
+        assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
+        script.verifyTimeZoneNotChanged();
+
+        assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
+        script.verifyTimeZoneChangedAndReset(timeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
+
+        assertTrue(mTimeZoneDetectorStrategy.confirmTimeZone(timeZoneId));
+        // The strategy checks the current confidence and if it is already high it takes no action.
+        script.verifyTimeZoneNotChanged();
+
+        assertFalse(mTimeZoneDetectorStrategy.confirmTimeZone(incorrectTimeZoneId));
+        script.verifyTimeZoneNotChanged();
+    }
+
+    @Test
     public void testGenerateMetricsState_enhancedMetricsCollection() {
         testGenerateMetricsState(true);
     }
@@ -984,7 +1062,7 @@
         String expectedDeviceTimeZoneId = "InitialZoneId";
 
         Script script = new Script()
-                .initializeTimeZoneSetting(expectedDeviceTimeZoneId)
+                .initializeTimeZoneSetting(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(expectedInternalConfig)
                 .resetConfigurationTracking();
 
@@ -1024,7 +1102,7 @@
 
         expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
         script.simulateConfigurationInternalChange(expectedInternalConfig)
-                .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId);
+                .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
         assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
                 manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
                 MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
@@ -1110,10 +1188,16 @@
     static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
 
         private final TestState<String> mTimeZoneId = new TestState<>();
+        private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
         private ConfigurationInternal mConfigurationInternal;
         private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
         private ConfigurationChangeListener mConfigurationInternalChangeListener;
 
+        FakeEnvironment() {
+            // Ensure the fake environment starts with the defaults a fresh device would.
+            initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
+        }
+
         void initializeConfig(ConfigurationInternal configurationInternal) {
             mConfigurationInternal = configurationInternal;
         }
@@ -1122,8 +1206,9 @@
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
 
-        void initializeTimeZoneSetting(String zoneId) {
+        void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
             mTimeZoneId.init(zoneId);
+            mTimeZoneConfidence.init(timeZoneConfidence);
         }
 
         void incrementClock() {
@@ -1141,18 +1226,20 @@
         }
 
         @Override
-        public boolean isDeviceTimeZoneInitialized() {
-            return mTimeZoneId.getLatest() != null;
-        }
-
-        @Override
         public String getDeviceTimeZone() {
             return mTimeZoneId.getLatest();
         }
 
         @Override
-        public void setDeviceTimeZone(String zoneId) {
+        public int getDeviceTimeZoneConfidence() {
+            return mTimeZoneConfidence.getLatest();
+        }
+
+        @Override
+        public void setDeviceTimeZoneAndConfidence(
+                String zoneId, @TimeZoneConfidence int confidence, String logInfo) {
             mTimeZoneId.set(zoneId);
+            mTimeZoneConfidence.set(confidence);
         }
 
         void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1162,16 +1249,22 @@
 
         void assertTimeZoneNotChanged() {
             mTimeZoneId.assertHasNotBeenSet();
+            mTimeZoneConfidence.assertHasNotBeenSet();
         }
 
-        void assertTimeZoneChangedTo(String timeZoneId) {
+        void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
             mTimeZoneId.assertHasBeenSet();
             mTimeZoneId.assertChangeCount(1);
             mTimeZoneId.assertLatestEquals(timeZoneId);
+
+            mTimeZoneConfidence.assertHasBeenSet();
+            mTimeZoneConfidence.assertChangeCount(1);
+            mTimeZoneConfidence.assertLatestEquals(confidence);
         }
 
         void commitAllChanges() {
             mTimeZoneId.commitLatest();
+            mTimeZoneConfidence.commitLatest();
         }
 
         @Override
@@ -1179,6 +1272,16 @@
         public long elapsedRealtimeMillis() {
             return mElapsedRealtimeMillis;
         }
+
+        @Override
+        public void addDebugLogEntry(String logMsg) {
+            // No-op for tests
+        }
+
+        @Override
+        public void dumpDebugLog(PrintWriter printWriter) {
+            // No-op for tests
+        }
     }
 
     /**
@@ -1187,8 +1290,9 @@
      */
     private class Script {
 
-        Script initializeTimeZoneSetting(String zoneId) {
-            mFakeEnvironment.initializeTimeZoneSetting(zoneId);
+        Script initializeTimeZoneSetting(
+                String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
+            mFakeEnvironment.initializeTimeZoneSetting(zoneId, timeZoneConfidence);
             return this;
         }
 
@@ -1280,29 +1384,27 @@
         }
 
         /** Verifies the device's time zone has been set and clears change tracking history. */
-        Script verifyTimeZoneChangedAndReset(String zoneId) {
-            mFakeEnvironment.assertTimeZoneChangedTo(zoneId);
+        Script verifyTimeZoneChangedAndReset(String zoneId, @TimeZoneConfidence int confidence) {
+            mFakeEnvironment.assertTimeZoneChangedTo(zoneId, confidence);
             mFakeEnvironment.commitAllChanges();
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
-            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
-            mFakeEnvironment.commitAllChanges();
+            verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
-            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
-            mFakeEnvironment.commitAllChanges();
+            verifyTimeZoneChangedAndReset(suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
             assertEquals("Only use this method with unambiguous geo suggestions",
                     1, suggestion.getZoneIds().size());
-            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneIds().get(0));
-            mFakeEnvironment.commitAllChanges();
+            verifyTimeZoneChangedAndReset(
+                    suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH);
             return this;
         }
 
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 bc2c57e..3adee0f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -24,7 +24,6 @@
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
@@ -32,6 +31,7 @@
 import android.content.res.Configuration;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
+import android.util.TimeSparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 617a34f..91c2fe0 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -54,6 +54,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -83,7 +84,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -101,6 +101,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Spy;
 
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -137,8 +138,8 @@
     private PackageManager mPackageManager;
     @Mock
     private IBinder mBinder;
-    @Mock
-    private DreamManagerInternal mDreamManager;
+    @Spy
+    private TestInjector mInjector;
     @Captor
     private ArgumentCaptor<Intent> mOrderedBroadcastIntent;
     @Captor
@@ -207,10 +208,10 @@
         addLocalService(WindowManagerInternal.class, mWindowManager);
         addLocalService(PowerManagerInternal.class, mLocalPowerManager);
         addLocalService(TwilightManager.class, mTwilightManager);
-        addLocalService(DreamManagerInternal.class, mDreamManager);
-        
+
+        mInjector = spy(new TestInjector());
         mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
-                mTwilightManager, new TestInjector());
+                mTwilightManager, mInjector);
         try {
             mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         } catch (SecurityException e) {/* ignore for permission denial */}
@@ -1321,84 +1322,53 @@
 
     @Test
     public void dreamWhenDocked() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
-
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
-    }
-
-    @Test
-    public void noDreamWhenDocked_dreamsDisabled() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(false);
-
-        triggerDockIntent();
-        verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
-    }
-
-    @Test
-    public void noDreamWhenDocked_dreamsWhenDockedDisabled() {
-        setScreensaverActivateOnDock(false);
-        setScreensaverEnabled(true);
-
-        triggerDockIntent();
-        verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void noDreamWhenDocked_keyguardNotShowing_interactive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
         when(mPowerManager.isInteractive()).thenReturn(true);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
+        verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardShowing_interactive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardNotShowing_notInteractive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardShowing_notInteractive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     private void triggerDockIntent() {
@@ -1435,22 +1405,6 @@
                 mOrderedBroadcastIntent.getValue());
     }
 
-    private void setScreensaverEnabled(boolean enable) {
-        Settings.Secure.putIntForUser(
-                mContentResolver,
-                Settings.Secure.SCREENSAVER_ENABLED,
-                enable ? 1 : 0,
-                UserHandle.USER_CURRENT);
-    }
-
-    private void setScreensaverActivateOnDock(boolean enable) {
-        Settings.Secure.putIntForUser(
-                mContentResolver,
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
-                enable ? 1 : 0,
-                UserHandle.USER_CURRENT);
-    }
-
     private void requestAllPossibleProjectionTypes() throws RemoteException {
         for (int i = 0; i < Integer.SIZE; ++i) {
             mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
@@ -1467,11 +1421,17 @@
         }
 
         public TestInjector(int callingUid) {
-          this.callingUid = callingUid;
+            this.callingUid = callingUid;
         }
 
+        @Override
         public int getCallingUid() {
             return callingUid;
         }
+
+        @Override
+        public void startDreamWhenDockedIfAppropriate(Context context) {
+            // do nothing
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
new file mode 100644
index 0000000..ad47773
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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_A;
+import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
+import static android.view.KeyEvent.KEYCODE_B;
+import static android.view.KeyEvent.KEYCODE_C;
+import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
+import static android.view.KeyEvent.KEYCODE_E;
+import static android.view.KeyEvent.KEYCODE_L;
+import static android.view.KeyEvent.KEYCODE_M;
+import static android.view.KeyEvent.KEYCODE_META_LEFT;
+import static android.view.KeyEvent.KEYCODE_N;
+import static android.view.KeyEvent.KEYCODE_P;
+import static android.view.KeyEvent.KEYCODE_S;
+import static android.view.KeyEvent.KEYCODE_SLASH;
+import static android.view.KeyEvent.KEYCODE_SPACE;
+import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_Z;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+public class ModifierShortcutTests extends ShortcutKeyTestBase {
+    private static final SparseArray<String> META_SHORTCUTS =  new SparseArray<>();
+    static {
+        META_SHORTCUTS.append(KEYCODE_A, Intent.CATEGORY_APP_CALCULATOR);
+        META_SHORTCUTS.append(KEYCODE_B, Intent.CATEGORY_APP_BROWSER);
+        META_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
+        META_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
+        META_SHORTCUTS.append(KEYCODE_L, Intent.CATEGORY_APP_CALENDAR);
+        META_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
+        META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
+        META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
+    }
+
+    /**
+     * Test meta+ shortcuts defined in bookmarks.xml.
+     */
+    @Test
+    public void testMetaShortcuts() {
+        for (int i = 0; i < META_SHORTCUTS.size(); i++) {
+            final int keyCode = META_SHORTCUTS.keyAt(i);
+            final String category = META_SHORTCUTS.valueAt(i);
+
+            sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
+            mPhoneWindowManager.assertLaunchCategory(category);
+        }
+    }
+
+    /**
+     * ALT + TAB to show recent apps.
+     */
+    @Test
+    public void testAltTab() {
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0);
+        mPhoneWindowManager.assertShowRecentApps();
+    }
+
+    /**
+     * CTRL + SPACE to switch keyboard layout.
+     */
+    @Test
+    public void testCtrlSpace() {
+        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
+        mPhoneWindowManager.assertSwitchKeyboardLayout();
+    }
+
+    /**
+     * META + SPACE to switch keyboard layout.
+     */
+    @Test
+    public void testMetaSpace() {
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SPACE}, 0);
+        mPhoneWindowManager.assertSwitchKeyboardLayout();
+    }
+
+    /**
+     * CTRL + ALT + Z to enable accessibility service.
+     */
+    @Test
+    public void testCtrlAltZ() {
+        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0);
+        mPhoneWindowManager.assertAccessibilityKeychordCalled();
+    }
+
+    /**
+     * META + CTRL+ S to take screenshot.
+     */
+    @Test
+    public void testMetaCtrlS() {
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0);
+        mPhoneWindowManager.assertTakeScreenshotCalled();
+    }
+
+    /**
+     * META + N to expand notification panel.
+     */
+    @Test
+    public void testMetaN() throws RemoteException {
+        mPhoneWindowManager.overrideExpandNotificationsPanel();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
+        mPhoneWindowManager.assertExpandNotification();
+    }
+
+    /**
+     * META + SLASH to toggle shortcuts menu.
+     */
+    @Test
+    public void testMetaSlash() {
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0);
+        mPhoneWindowManager.assertToggleShortcutsMenu();
+    }
+
+    /**
+     * META  + ALT to toggle Cap Lock.
+     */
+    @Test
+    public void testMetaAlt() {
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
+        mPhoneWindowManager.assertToggleCapsLock();
+    }
+}
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 ee11ac8..a76b82b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -32,6 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
@@ -53,15 +54,16 @@
 import android.app.NotificationManager;
 import android.app.SearchManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
+import android.os.RemoteException;
 import android.os.Vibrator;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
@@ -73,12 +75,17 @@
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.DisplayPolicy;
 import com.android.server.wm.DisplayRotation;
 import com.android.server.wm.WindowManagerInternal;
 
+import junit.framework.Assert;
+
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockSettings;
 import org.mockito.Mockito;
@@ -118,6 +125,8 @@
     @Mock private GlobalActions mGlobalActions;
     @Mock private AccessibilityShortcutController mAccessibilityShortcutController;
 
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
     private StaticMockitoSession mMockitoSession;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -226,6 +235,8 @@
         mPhoneWindowManager.systemBooted();
 
         overrideLaunchAccessibility();
+        doReturn(false).when(mPhoneWindowManager).keyguardOn();
+        doNothing().when(mContext).startActivityAsUser(any(), any());
     }
 
     void tearDown() {
@@ -310,6 +321,22 @@
         doReturn(true).when(mTelecomManager).endCall();
     }
 
+    void overrideExpandNotificationsPanel() {
+        // Can't directly mock on IStatusbarService, use spyOn and override the specific api.
+        mPhoneWindowManager.getStatusBarService();
+        spyOn(mPhoneWindowManager.mStatusBarService);
+        try {
+            doNothing().when(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    void overrideStatusBarManagerInternal() {
+        doReturn(mStatusBarManagerInternal).when(
+                () -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -368,4 +395,46 @@
         waitForIdle();
         verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
     }
+
+    void assertLaunchCategory(String category) {
+        waitForIdle();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
+        Assert.assertTrue(intentCaptor.getValue().getSelector().hasCategory(category));
+        // Reset verifier for next call.
+        Mockito.reset(mContext);
+    }
+
+    void assertShowRecentApps() {
+        waitForIdle();
+        verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
+    }
+
+    void assertSwitchKeyboardLayout() {
+        waitForIdle();
+        verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), anyInt());
+    }
+
+    void assertTakeBugreport() {
+        waitForIdle();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
+                any(), anyInt(), any(), any());
+        Assert.assertTrue(intentCaptor.getValue().getAction() == Intent.ACTION_BUG_REPORT);
+    }
+
+    void assertExpandNotification() throws RemoteException {
+        waitForIdle();
+        verify(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+    }
+
+    void assertToggleShortcutsMenu() {
+        waitForIdle();
+        verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
+    }
+
+    void assertToggleCapsLock() {
+        waitForIdle();
+        verify(mInputManagerInternal).toggleCapsLock(anyInt());
+    }
 }
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 333be7b..2f23e7f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2990,7 +2990,8 @@
                 .setSystemDecorations(true).build();
         // Add a decor insets provider window.
         final WindowState navbar = createNavBarWithProvidedInsets(squareDisplay);
-        squareDisplay.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(navbar);
+        assertTrue(navbar.providesNonDecorInsets()
+                && squareDisplay.getDisplayPolicy().updateDecorInsetsInfo());
         squareDisplay.sendNewConfiguration();
         final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c2ca0a2..c3d49e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
@@ -40,6 +41,7 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
+import android.window.BackAnimationAdapter;
 import android.window.BackEvent;
 import android.window.BackNavigationInfo;
 import android.window.IOnBackInvokedCallback;
@@ -54,6 +56,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -61,17 +64,18 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class BackNavigationControllerTests extends WindowTestsBase {
-
     private BackNavigationController mBackNavigationController;
     private WindowManagerInternal mWindowManagerInternal;
+    private BackAnimationAdapter mBackAnimationAdapter;
 
     @Before
     public void setUp() throws Exception {
-        mBackNavigationController = new BackNavigationController();
+        mBackNavigationController = Mockito.spy(new BackNavigationController());
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         mWindowManagerInternal = mock(WindowManagerInternal.class);
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
         mBackNavigationController.setWindowManager(mWm);
+        mBackAnimationAdapter = mock(BackAnimationAdapter.class);
     }
 
     @Test
@@ -79,14 +83,16 @@
         Task task = createTopTaskWithActivity();
         IOnBackInvokedCallback callback = withSystemCallback(task);
 
-        BackNavigationInfo backNavigationInfo =
-                mBackNavigationController.startBackNavigation(true, null);
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
-        assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
-        assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
         assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+
+        // verify if back animation would start.
+        verify(mBackNavigationController).scheduleAnimationLocked(
+                eq(BackNavigationInfo.TYPE_RETURN_TO_HOME), any(), eq(mBackAnimationAdapter),
+                any());
     }
 
     @Test
@@ -114,10 +120,6 @@
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
         assertWithMessage("Activity callback").that(
                 backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
-
-        // Until b/207481538 is implemented, this should be null
-        assertThat(backNavigationInfo.getScreenshotSurface()).isNull();
-        assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNull();
     }
 
     @Test
@@ -233,7 +235,7 @@
 
     @Nullable
     private BackNavigationInfo startBackNavigation() {
-        return mBackNavigationController.startBackNavigation(true, null);
+        return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter);
     }
 
     @NonNull
@@ -287,6 +289,7 @@
                 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
         WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window");
         when(record.mSurfaceControl.isValid()).thenReturn(true);
+        Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
         mAtm.setFocusedTask(task.mTaskId, record);
         addToWindowMap(window, true);
         return task;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e2c94c5..49fd1ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -296,6 +296,14 @@
     }
 
     @Test
+    public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        mContentRecorder.setContentRecordingSession(null);
+        mTask.removeImmediately();
+    }
+
+    @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
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 262b141..a980765 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -292,12 +292,16 @@
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
         final DisplayInfo di = mDisplayContent.getDisplayInfo();
         final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
-        assertTrue(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+        assertTrue(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
         assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
                 di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
         mDisplayContent.sendNewConfiguration();
         assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
-        assertFalse(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+        assertFalse(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
+
+        navbar.removeIfPossible();
+        assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth,
+                di.logicalHeight).mNonDecorInsets.bottom);
     }
 
     @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
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 2956c14..ac3d0f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -46,6 +46,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -55,6 +56,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.google.android.collect.Lists;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,7 +93,11 @@
     @Before
     public void setUp() {
         // Let the Display to be created with the DualDisplay policy.
-        final DisplayAreaPolicy.Provider policyProvider = new DualDisplayTestPolicyProvider();
+        setupDisplay(new DualDisplayTestPolicyProvider(mWm));
+    }
+
+    /** Populates fields for the test display. */
+    private void setupDisplay(@NonNull DisplayAreaPolicy.Provider policyProvider) {
         doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
         // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait).
@@ -383,6 +390,36 @@
     }
 
     @Test
+    public void testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder() {
+        // Define the DualDisplayArea hierarchy without IME_PLACEHOLDER in DAGs.
+        setupDisplay(new DualDisplayTestPolicyProvider(new ArrayList<>(), new ArrayList<>()));
+        setupImeWindow();
+        final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
+        final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
+
+        // By default, the ime container is attached to DC as defined in DAPolicy.
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
+
+        // firstActivityWin should be the target
+        final WindowState firstActivityWin =
+                createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
+                        "firstActivityWin");
+        spyOn(firstActivityWin);
+        doReturn(true).when(firstActivityWin).canBeImeTarget();
+        WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
+
+        // There is no IME_PLACEHOLDER in the firstRoot, so the ImeContainer will not be reparented.
+        assertThat(imeTarget).isEqualTo(firstActivityWin);
+        verify(mFirstRoot).placeImeContainer(imeContainer);
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
+        assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
+                .isEqualTo(FEATURE_IME_PLACEHOLDER);
+        assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
+        assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull();
+        assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull();
+    }
+
+    @Test
     public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
@@ -523,9 +560,37 @@
     /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */
     static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider {
 
+        @NonNull
+        private final List<DisplayAreaPolicyBuilder.Feature> mFirstRootFeatures = new ArrayList<>();
+        @NonNull
+        private final List<DisplayAreaPolicyBuilder.Feature> mSecondRootFeatures =
+                new ArrayList<>();
+
+        DualDisplayTestPolicyProvider(@NonNull WindowManagerService wmService) {
+            // Add IME_PLACEHOLDER by default.
+            this(Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder(
+                            wmService.mPolicy,
+                            "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
+                            .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
+                            .build()),
+                    Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder(
+                            wmService.mPolicy,
+                            "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
+                            .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
+                            .build()));
+        }
+
+        DualDisplayTestPolicyProvider(
+                @NonNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures,
+                @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures) {
+            mFirstRootFeatures.addAll(firstRootFeatures);
+            mSecondRootFeatures.addAll(secondRootFeatures);
+        }
+
         @Override
-        public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
-                RootDisplayArea root, DisplayArea.Tokens imeContainer) {
+        public DisplayAreaPolicy instantiate(@NonNull WindowManagerService wmService,
+                @NonNull DisplayContent content, @NonNull RootDisplayArea root,
+                @NonNull DisplayArea.Tokens imeContainer) {
             // Root
             // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation
             // layer
@@ -554,12 +619,10 @@
             firstTdaList.add(firstTaskDisplayArea);
             DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy =
                     new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot)
-                            .setTaskDisplayAreas(firstTdaList)
-                            .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
-                                    wmService.mPolicy,
-                                    "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
-                                    .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
-                                    .build());
+                            .setTaskDisplayAreas(firstTdaList);
+            for (DisplayAreaPolicyBuilder.Feature feature : mFirstRootFeatures) {
+                firstHierarchy.addFeature(feature);
+            }
 
             // Second
             final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot",
@@ -570,12 +633,10 @@
             secondTdaList.add(secondTaskDisplayArea);
             DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy =
                     new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot)
-                            .setTaskDisplayAreas(secondTdaList)
-                            .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
-                                    wmService.mPolicy,
-                                    "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
-                                    .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
-                                    .build());
+                            .setTaskDisplayAreas(secondTdaList);
+            for (DisplayAreaPolicyBuilder.Feature feature : mSecondRootFeatures) {
+                secondHierarchy.addFeature(feature);
+            }
 
             return new DisplayAreaPolicyBuilder()
                     .setRootHierarchy(rootHierarchy)
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index d487113..3090590 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -78,7 +78,7 @@
     public void setUp() throws Exception {
         // Let the Display be created with the DualDisplay policy.
         final DisplayAreaPolicy.Provider policyProvider =
-                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
+                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm);
         Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
         mWindowContext = new InputMethodDialogWindowContext();
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 adf694c..5def703 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
 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;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,34 @@
 
     @Test
     public void testCreateRecentTaskInfo_detachedTask() {
-        final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+        final Task task = createTaskBuilder(".Task").build();
+        new ActivityBuilder(mSupervisor.mService)
+                .setTask(task)
+                .setUid(NOBODY_UID)
+                .setComponent(getUniqueComponentName())
+                .build();
         final TaskDisplayArea tda = task.getDisplayArea();
 
         assertTrue(task.isAttached());
         assertTrue(task.supportsMultiWindow());
 
-        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                false /* getTasksAllowed */);
+
+        assertTrue(info.topActivity == null);
+        assertTrue(info.topActivityInfo == null);
+        assertTrue(info.baseActivity == null);
+
         // The task can be put in split screen even if it is not attached now.
         task.removeImmediately();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
@@ -1242,7 +1257,8 @@
         doReturn(false).when(tda).supportsNonResizableMultiWindow();
         doReturn(false).when(task).isResizeable();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertFalse(info.supportsMultiWindow);
 
@@ -1250,7 +1266,8 @@
         // the device supports it.
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
     }
@@ -1532,7 +1549,7 @@
 
         @Override
         void getTasks(int maxNum, List<RunningTaskInfo> list, int flags, RecentTasks recentTasks,
-                WindowContainer root, int callingUid, ArraySet<Integer> profileIds) {
+                WindowContainer<?> root, int callingUid, ArraySet<Integer> profileIds) {
             mLastAllowed = (flags & FLAG_ALLOWED) == FLAG_ALLOWED;
             super.getTasks(maxNum, list, flags, recentTasks, root, callingUid, profileIds);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a1d6a50..a2b4cb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -114,6 +114,7 @@
         assertTrue(recentActivity.mVisibleRequested);
         assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
                 mAtm.mDemoteTopAppReasons);
+        assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
 
         // Simulate the animation is cancelled without changing the stack order.
         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 6128428..b46e90d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1312,13 +1312,15 @@
         secondActivity.app.setThread(null);
         // This should do nothing from a non-attached caller.
         assertFalse(task.navigateUpTo(secondActivity /* source record */,
-                firstActivity.intent /* destIntent */, null /* destGrants */,
-                0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+                firstActivity.intent /* destIntent */, null /* resolvedType */,
+                null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+                null /* resultGrants */));
 
         secondActivity.app.setThread(thread);
         assertTrue(task.navigateUpTo(secondActivity /* source record */,
-                firstActivity.intent /* destIntent */, null /* destGrants */,
-                0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+                firstActivity.intent /* destIntent */, null /* resolvedType */,
+                null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+                null /* resultGrants */));
         // The firstActivity uses default launch mode, so the activities between it and itself will
         // be finished.
         assertTrue(secondActivity.finishing);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 8cd8e9b..736f8f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -41,9 +41,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
 import android.view.IWindowManager;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
@@ -51,7 +51,9 @@
 import android.view.cts.surfacevalidator.PixelColor;
 import android.view.cts.surfacevalidator.SaveBitmapHelper;
 import android.window.ScreenCapture;
-import android.window.ScreenCapture.SyncScreenCaptureListener;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -141,7 +143,7 @@
     }
 
     @Test
-    public void testCaptureDisplay() throws RemoteException {
+    public void testCaptureDisplay() throws Exception {
         IWindowManager windowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         SurfaceControl sc = new SurfaceControl.Builder()
@@ -168,9 +170,10 @@
                 .setPosition(sc, point.x, point.y)
                 .apply(true);
 
-        SyncScreenCaptureListener listener = new SyncScreenCaptureListener();
-        windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener());
-        ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot();
+        Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
+        windowManager.captureDisplay(DEFAULT_DISPLAY, null, syncScreenCapture.first);
+        ScreenshotHardwareBuffer hardwareBuffer = syncScreenCapture.second.get();
         assertNotNull(hardwareBuffer);
 
         Bitmap screenshot = hardwareBuffer.asBitmap();
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 f5fc5c1..70e6f29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -39,6 +39,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -81,6 +82,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
@@ -93,6 +95,7 @@
 import org.mockito.MockSettings;
 import org.mockito.Mockito;
 import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -283,6 +286,16 @@
         // StatusBarManagerInternal
         final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
         doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+
+        // UserManagerInternal
+        final UserManagerInternal umi = mock(UserManagerInternal.class);
+        doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
+        Answer<Boolean> isUserVisibleAnswer = invocation -> {
+            int userId = invocation.getArgument(0);
+            return userId == mWmService.mCurrentUserId;
+        };
+        when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
+        when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
     }
 
     private void setUpActivityTaskManagerService() {
@@ -403,6 +416,7 @@
         LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
     }
 
     Description getDescription() {
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 9bdf750..1404de2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
 import static android.window.TaskFragmentOrganizer.getTransitionType;
@@ -76,6 +77,7 @@
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizerToken;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -271,7 +273,7 @@
     @Test
     public void testOnTaskFragmentParentInfoChanged() {
         setupMockParent(mTaskFragment, mTask);
-        mTask.getConfiguration().smallestScreenWidthDp = 10;
+        mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 10;
 
         mController.onTaskFragmentAppeared(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
@@ -295,7 +297,7 @@
 
         // Trigger callback if the size is changed.
         clearInvocations(mOrganizer);
-        mTask.getConfiguration().smallestScreenWidthDp = 100;
+        mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 100;
         mController.onTaskFragmentInfoChanged(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
@@ -304,7 +306,8 @@
 
         // Trigger callback if the windowing mode is changed.
         clearInvocations(mOrganizer);
-        mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+        mTask.getTaskFragmentParentInfo().getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_PINNED);
         mController.onTaskFragmentInfoChanged(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
@@ -727,6 +730,16 @@
     }
 
     @Test
+    public void testApplyTransaction_finishActivity() {
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+
+        mTransaction.finishActivity(activity.token);
+        assertApplyTransactionAllowed(mTransaction);
+
+        assertTrue(activity.finishing);
+    }
+
+    @Test
     public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
         mController.unregisterOrganizer(mIOrganizer);
         final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
@@ -1268,7 +1281,7 @@
         final TaskFragmentTransaction.Change change = changes.get(0);
         assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
         assertEquals(task.mTaskId, change.getTaskId());
-        assertEquals(task.getConfiguration(), change.getTaskConfiguration());
+        assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
     }
 
     /** Asserts that there will be a transaction for TaskFragment error. */
@@ -1316,8 +1329,8 @@
     /** Setups the mock Task as the parent of the given TaskFragment. */
     private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
         doReturn(mockParent).when(taskFragment).getTask();
-        final Configuration taskConfig = new Configuration();
-        doReturn(taskConfig).when(mockParent).getConfiguration();
+        doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true))
+                .when(mockParent).getTaskFragmentParentInfo();
 
         // Task needs to be visible
         mockParent.lastActiveTime = 100;
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 e2dff96..52280d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -395,6 +396,70 @@
     }
 
     @Test
+    public void testCreateInfo_PromoteSimilarClose() {
+        final Transition transition = createTestTransition(TRANSIT_CLOSE);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task topTask = createTask(mDisplayContent);
+        final Task belowTask = createTask(mDisplayContent);
+        final ActivityRecord showing = createActivityRecord(belowTask);
+        final ActivityRecord hiding = createActivityRecord(topTask);
+        final ActivityRecord closing = createActivityRecord(topTask);
+        // Start states.
+        changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+        fillChangeMap(changes, topTask);
+        // End states.
+        showing.mVisibleRequested = true;
+        closing.mVisibleRequested = false;
+        hiding.mVisibleRequested = false;
+
+        participants.add(belowTask);
+        participants.add(hiding);
+        participants.add(closing);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        assertEquals(2, targets.size());
+        assertTrue(targets.contains(belowTask));
+        assertTrue(targets.contains(topTask));
+    }
+
+    @Test
+    public void testCreateInfo_PromoteSimilarOpen() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task topTask = createTask(mDisplayContent);
+        final Task belowTask = createTask(mDisplayContent);
+        final ActivityRecord showing = createActivityRecord(topTask);
+        final ActivityRecord opening = createActivityRecord(topTask);
+        final ActivityRecord closing = createActivityRecord(belowTask);
+        // Start states.
+        changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+        changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        fillChangeMap(changes, topTask);
+        // End states.
+        showing.mVisibleRequested = true;
+        opening.mVisibleRequested = true;
+        closing.mVisibleRequested = false;
+
+        participants.add(belowTask);
+        participants.add(showing);
+        participants.add(opening);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        assertEquals(2, targets.size());
+        assertTrue(targets.contains(belowTask));
+        assertTrue(targets.contains(topTask));
+    }
+
+    @Test
     public void testTargets_noIntermediatesToWallpaper() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
 
@@ -734,6 +799,11 @@
         assertTrue(asyncRotationController.isTargetToken(decorToken));
         assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
+        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+            // Only seamless window syncs its draw transaction with transition.
+            assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
+            assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
+        }
         screenDecor.setOrientationChanging(false);
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
         // executed until the transaction is committed, so it is still in target tokens.
@@ -1077,6 +1147,39 @@
     }
 
     @Test
+    public void testIsBehindStartingWindowChange() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity0 = createActivityRecord(task);
+        final ActivityRecord activity1 = createActivityRecord(task);
+        doReturn(true).when(activity1).hasStartingWindow();
+
+        // Start states.
+        changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        // End states.
+        activity0.mVisibleRequested = false;
+        activity1.mVisibleRequested = true;
+
+        participants.add(activity0);
+        participants.add(activity1);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
+        // window should cover the whole Task.
+        assertEquals(2, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+        assertTrue(info.getChanges().get(1).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+
+    }
+
+    @Test
     public void testFlagInTaskWithEmbeddedActivity() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
@@ -1123,7 +1226,7 @@
     }
 
     @Test
-    public void testFlagFillsTask() {
+    public void testFlagFillsTask_embeddingNotFillingTask() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
         final ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -1168,6 +1271,67 @@
     }
 
     @Test
+    public void testFlagFillsTask_openActivityFillingTask() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        // Set to multi-windowing mode in order to set bounds.
+        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect taskBounds = new Rect(0, 0, 500, 1000);
+        task.setBounds(taskBounds);
+        final ActivityRecord activity = createActivityRecord(task);
+        // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
+        activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+        activity.mVisibleRequested = false;
+        changes.put(activity, new Transition.ChangeInfo(activity));
+        // End states: reset bounds to fill Task.
+        activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
+        activity.mVisibleRequested = true;
+
+        participants.add(activity);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // Opening activity that is filling Task after transition should have the flag.
+        assertEquals(1, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+    }
+
+    @Test
+    public void testFlagFillsTask_closeActivityFillingTask() {
+        final Transition transition = createTestTransition(TRANSIT_CLOSE);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        // Set to multi-windowing mode in order to set bounds.
+        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect taskBounds = new Rect(0, 0, 500, 1000);
+        task.setBounds(taskBounds);
+        final ActivityRecord activity = createActivityRecord(task);
+        // Start states: fills Task without override.
+        activity.mVisibleRequested = true;
+        changes.put(activity, new Transition.ChangeInfo(activity));
+        // End states: set bounds to make sure the start bounds is ignored if it is not visible.
+        activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+        activity.mVisibleRequested = false;
+
+        participants.add(activity);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // Closing activity that is filling Task before transition should have the flag.
+        assertEquals(1, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+    }
+
+    @Test
     public void testIncludeEmbeddedActivityReparent() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index 1685673..f6d0bf1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -213,7 +213,7 @@
     public void testImeSwitchDialogWindowTokenRemovedOnDualDisplayContent_ListenToImeContainer() {
         // Let the Display to be created with the DualDisplay policy.
         final DisplayAreaPolicy.Provider policyProvider =
-                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
+                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(mWm);
         doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
         // Create a DisplayContent with dual RootDisplayArea
         DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent =
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 8b63904..8370691 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -20,6 +20,7 @@
 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.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -49,22 +50,29 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.eq;
 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.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.VirtualDisplay;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.view.IWindowSessionCallback;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsVisibilities;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
@@ -87,6 +95,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowManagerServiceTests extends WindowTestsBase {
+
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
@@ -189,14 +198,25 @@
         mWm.mWindowMap.put(win.mClient.asBinder(), win);
         final int w = 100;
         final int h = 200;
+        final ClientWindowFrames outFrames = new ClientWindowFrames();
+        final MergedConfiguration outConfig = new MergedConfiguration();
+        final SurfaceControl outSurfaceControl = new SurfaceControl();
+        final InsetsState outInsetsState = new InsetsState();
+        final InsetsSourceControl[] outControls = new InsetsSourceControl[0];
+        final Bundle outBundle = new Bundle();
         mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
-                new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
-                new InsetsState(), new InsetsSourceControl[0], new Bundle());
+                outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
         // Because the window is already invisible, it doesn't need to apply exiting animation
         // and WMS#tryStartExitingAnimation() will destroy the surface directly.
         assertFalse(win.mAnimatingExit);
         assertFalse(win.mHasSurface);
         assertNull(win.mWinAnimator.mSurfaceController);
+
+        doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
+        // Invisible requested activity should not get the last config even if its view is visible.
+        mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
+                outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
+        assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
     }
 
     @Test
@@ -321,14 +341,22 @@
 
     @Test
     public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() {
-        boolean currentTouchMode = mWm.getInTouchMode();
+        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(true).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
         doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
         when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
                 android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
 
-        mWm.setInTouchMode(!currentTouchMode);
+        mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
 
         verify(mWm.mInputManager).setInTouchMode(
                 !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
@@ -337,14 +365,22 @@
 
     @Test
     public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() {
-        boolean currentTouchMode = mWm.getInTouchMode();
+        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(true).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
         doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
         when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
                 android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(false);
 
-        mWm.setInTouchMode(!currentTouchMode);
+        mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
 
         verify(mWm.mInputManager).setInTouchMode(
                 !currentTouchMode, callingPid, callingUid, /* hasPermission= */ false,
@@ -352,6 +388,83 @@
     }
 
     @Test
+    public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
+        // Create one extra display
+        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final int numberOfDisplays = mWm.mRoot.mChildren.size();
+        assertThat(numberOfDisplays).isAtLeast(2);
+
+        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(false).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
+
+        verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+                eq(!currentTouchMode), eq(callingPid), eq(callingUid),
+                /* hasPermission= */ eq(true), /* displayId= */ anyInt());
+    }
+
+    @Test
+    public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+        // Create one extra display
+        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final int numberOfDisplays = mWm.mRoot.mChildren.size();
+        assertThat(numberOfDisplays).isAtLeast(2);
+
+        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(true).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+        // Ensure that new display touch mode state has changed.
+        verify(mWm.mInputManager).setInTouchMode(
+                !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+                virtualDisplay.getDisplay().getDisplayId());
+    }
+
+    private VirtualDisplay createVirtualDisplay() {
+        // Create virtual display
+        Point surfaceSize = new Point(
+                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+        VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
+                surfaceSize.x, surfaceSize.y,
+                DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+        final int displayId = virtualDisplay.getDisplay().getDisplayId();
+        mWm.mRoot.onDisplayAdded(displayId);
+
+        // Ensure that virtual display was properly created and stored in WRC
+        assertThat(mWm.mRoot.getDisplayContent(
+                virtualDisplay.getDisplay().getDisplayId())).isNotNull();
+
+        return virtualDisplay;
+    }
+
+    @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
         WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
         assertThat(wct).isNull();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e8c8054..1636667 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -735,6 +735,15 @@
         assertTrue(mWm.mResizingWindows.contains(startingApp));
         assertTrue(startingApp.isDrawn());
         assertFalse(startingApp.getOrientationChanging());
+
+        // Even if the display is frozen, invisible requested window should not be affected.
+        startingApp.mActivityRecord.mVisibleRequested = false;
+        mWm.startFreezingDisplay(0, 0, mDisplayContent);
+        doReturn(true).when(mWm.mPolicy).isScreenOn();
+        startingApp.getWindowFrames().setInsetsChanged(true);
+        startingApp.updateResizingWindowIfNeeded();
+        assertTrue(startingApp.isDrawn());
+        assertFalse(startingApp.getOrientationChanging());
     }
 
     @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
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 9c9f5db..b6110247 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit, null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index eafcef2..1e74451 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -210,21 +210,15 @@
         final int translatedAppUid =
                 getAppUidByComponentName(getContext(), componentName, getUserId());
         final String packageName = componentName.getPackageName();
-        if (activityDestroyed) {
-            // In the Activity destroy case, we only calls onTranslationFinished() in
-            // non-finisTranslation() state. If there is a finisTranslation() calls by apps, we
-            // should remove the waiting callback to avoid callback twice.
+        // In the Activity destroyed case, we only call onTranslationFinished() in
+        // non-finishTranslation() state. If there is a finishTranslation() call by apps, we
+        // should remove the waiting callback to avoid invoking callbacks twice.
+        if (activityDestroyed || mWaitingFinishedCallbackActivities.contains(token)) {
             invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
                     /* sourceSpec= */ null, /* targetSpec= */ null,
                     packageName, translatedAppUid);
             mWaitingFinishedCallbackActivities.remove(token);
-        } else {
-            if (mWaitingFinishedCallbackActivities.contains(token)) {
-                invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
-                        /* sourceSpec= */ null, /* targetSpec= */ null,
-                        packageName, translatedAppUid);
-                mWaitingFinishedCallbackActivities.remove(token);
-            }
+            mActiveTranslations.remove(token);
         }
     }
 
@@ -237,6 +231,9 @@
         // Activity is the new Activity, the original Activity is paused in the same task.
         // To make sure the operation still work, we use the token to find the target Activity in
         // this task, not the top Activity only.
+        //
+        // Note: getAttachedNonFinishingActivityForTask() takes the shareable activity token. We
+        // call this method so that we can get the regular activity token below.
         ActivityTokens candidateActivityTokens =
                 mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token);
         if (candidateActivityTokens == null) {
@@ -263,27 +260,27 @@
                 getAppUidByComponentName(getContext(), componentName, getUserId());
         String packageName = componentName.getPackageName();
 
-        invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+        invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, token,
                 translatedAppUid);
-        updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+        updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, token,
                 translatedAppUid);
     }
 
     @GuardedBy("mLock")
     private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec,
-            TranslationSpec targetSpec, String packageName, IBinder activityToken,
+            TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
             int translatedAppUid) {
         // We keep track of active translations and their state so that we can:
         // 1. Trigger callbacks that are registered after translation has started.
         //    See registerUiTranslationStateCallbackLocked().
         // 2. NOT trigger callbacks when the state didn't change.
         //    See invokeCallbacksIfNecessaryLocked().
-        ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+        ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
         switch (state) {
             case STATE_UI_TRANSLATION_STARTED: {
                 if (activeTranslation == null) {
                     try {
-                        activityToken.linkToDeath(this, /* flags= */ 0);
+                        shareableActivityToken.linkToDeath(this, /* flags= */ 0);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to call linkToDeath for translated app with uid="
                                 + translatedAppUid + "; activity is already dead", e);
@@ -294,7 +291,7 @@
                                 packageName, translatedAppUid);
                         return;
                     }
-                    mActiveTranslations.put(activityToken,
+                    mActiveTranslations.put(shareableActivityToken,
                             new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid,
                                     packageName));
                 }
@@ -317,7 +314,7 @@
 
             case STATE_UI_TRANSLATION_FINISHED: {
                 if (activeTranslation != null) {
-                    mActiveTranslations.remove(activityToken);
+                    mActiveTranslations.remove(shareableActivityToken);
                 }
                 break;
             }
@@ -332,12 +329,12 @@
 
     @GuardedBy("mLock")
     private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec,
-            TranslationSpec targetSpec, String packageName, IBinder activityToken,
+            TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
             int translatedAppUid) {
         boolean shouldInvokeCallbacks = true;
         int stateForCallbackInvocation = state;
 
-        ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+        ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
         if (activeTranslation == null) {
             if (state != STATE_UI_TRANSLATION_STARTED) {
                 shouldInvokeCallbacks = false;
@@ -403,14 +400,6 @@
             }
         }
 
-        if (DEBUG) {
-            Slog.d(TAG,
-                    (shouldInvokeCallbacks ? "" : "NOT ")
-                            + "Invoking callbacks for translation state="
-                            + stateForCallbackInvocation + " for app with uid=" + translatedAppUid
-                            + " packageName=" + packageName);
-        }
-
         if (shouldInvokeCallbacks) {
             invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName,
                     translatedAppUid);
@@ -448,7 +437,7 @@
             pw.println(waitingFinishCallbackSize);
             for (IBinder activityToken : mWaitingFinishedCallbackActivities) {
                 pw.print(prefix);
-                pw.print("activityToken: ");
+                pw.print("shareableActivityToken: ");
                 pw.println(activityToken);
             }
         }
@@ -458,7 +447,14 @@
             int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName,
             int translatedAppUid) {
         Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName);
-        if (mCallbacks.getRegisteredCallbackCount() == 0) {
+        int registeredCallbackCount = mCallbacks.getRegisteredCallbackCount();
+        if (DEBUG) {
+            Slog.d(TAG, "Invoking " + registeredCallbackCount + " callbacks for translation state="
+                    + state + " for app with uid=" + translatedAppUid
+                    + " packageName=" + packageName);
+        }
+
+        if (registeredCallbackCount == 0) {
             return;
         }
         List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
@@ -521,8 +517,10 @@
     @GuardedBy("mLock")
     public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) {
         mCallbacks.register(callback, sourceUid);
-
-        if (mActiveTranslations.size() == 0) {
+        int numActiveTranslations = mActiveTranslations.size();
+        Slog.i(TAG, "New registered callback for sourceUid=" + sourceUid + " with currently "
+                + numActiveTranslations + " active translations");
+        if (numActiveTranslations == 0) {
             return;
         }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 26a1e9d..361b1c0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
@@ -29,6 +28,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeSparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 34c6c16..9203208 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -34,7 +34,6 @@
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventList;
 import android.app.usage.EventStats;
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
@@ -50,6 +49,7 @@
 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;
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9562c1a..e90a376 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -514,6 +514,8 @@
         private boolean mConfigured;
         private boolean mAudioAccessoryConnected;
         private boolean mAudioAccessorySupported;
+        private boolean mConnectedToDataDisabledPort;
+        private int mPowerBrickConnectionStatus;
 
         private UsbAccessory mCurrentAccessory;
         private int mUsbNotificationId;
@@ -952,12 +954,19 @@
                                 && status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
                                 DATA_ROLE_DEVICE)
                                 && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+
+                        boolean usbDataDisabled =
+                                status.getUsbDataStatus() != UsbPortStatus.DATA_STATUS_ENABLED;
+                        mConnectedToDataDisabledPort = status.isConnected() && usbDataDisabled;
+                        mPowerBrickConnectionStatus = status.getPowerBrickConnectionStatus();
                     } else {
                         mHostConnected = false;
                         mSourcePower = false;
                         mSinkPower = false;
                         mAudioAccessoryConnected = false;
                         mSupportsAllCombinations = false;
+                        mConnectedToDataDisabledPort = false;
+                        mPowerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
                     }
 
                     if (mHostConnected) {
@@ -1265,6 +1274,12 @@
             } else if (mHostConnected && mSinkPower && (mUsbCharging || mUsbAccessoryConnected)) {
                 titleRes = com.android.internal.R.string.usb_charging_notification_title;
                 id = SystemMessage.NOTE_USB_CHARGING;
+            } else if (mSinkPower && mConnectedToDataDisabledPort
+                    && mPowerBrickConnectionStatus != UsbPortStatus.POWER_BRICK_STATUS_CONNECTED) {
+                // Show charging notification when USB Data is disabled on the port, and not
+                // connected to a wall charger.
+                titleRes = com.android.internal.R.string.usb_charging_notification_title;
+                id = SystemMessage.NOTE_USB_CHARGING;
             }
             if (id != mUsbNotificationId || force) {
                 // clear notification if title needs changing
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a131084..921f6e2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -22,6 +22,7 @@
 import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
+import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
 import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
@@ -189,7 +190,7 @@
     final int mUser;
     final Context mContext;
 
-    @Nullable final AttentionManagerInternal mAttentionManagerInternal;
+    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
 
     final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
             this::setProximityMeters;
@@ -244,9 +245,11 @@
         mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
 
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
-        mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
-        if (mAttentionManagerInternal != null) {
-            mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+        if (ENABLE_PROXIMITY_RESULT) {
+            mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+            if (mAttentionManagerInternal != null) {
+                mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+            }
         }
 
         mLastRestartInstant = Instant.now();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4ee066c..79546b8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -95,6 +95,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.LatencyTracker;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -135,9 +136,6 @@
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
 
-    // TODO(b/226201975): remove once RoleService supports pre-created users
-    private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
     public VoiceInteractionManagerService(Context context) {
         super(context);
         mContext = context;
@@ -174,11 +172,11 @@
         mAmInternal.setVoiceInteractionManagerProvider(
                 new ActivityManagerInternal.VoiceInteractionManagerProvider() {
                     @Override
-                    public void notifyActivityEventChanged() {
+                    public void notifyActivityDestroyed(IBinder activityToken) {
                         if (DEBUG) {
-                            Slog.d(TAG, "call notifyActivityEventChanged");
+                            Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken);
                         }
-                        mServiceStub.notifyActivityEventChanged();
+                        mServiceStub.notifyActivityDestroyed(activityToken);
                     }
                 });
     }
@@ -191,6 +189,8 @@
             mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             mServiceStub.systemRunning(isSafeMode());
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            mServiceStub.registerVoiceInteractionSessionListener(mLatencyLoggingListener);
         }
     }
 
@@ -306,24 +306,14 @@
             return hotwordDetectionConnection.mIdentity;
         }
 
+        // TODO(b/226201975): remove this method once RoleService supports pre-created users
         @Override
         public void onPreCreatedUserConversion(int userId) {
-            Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
-            for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
-                UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
-                if (preCreatedUser.getIdentifier() == userId) {
-                    Slogf.d(TAG, "Updating role on pre-created user %d", userId);
-                    mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
-                            preCreatedUser);
-                    mIgnoredPreCreatedUsers.remove(i);
-                    return;
-                }
-            }
-            Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
-                    + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+            Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+                    userId);
+            mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+                                                UserHandle.of(userId));
         }
-
     }
 
     // implementation entry point and binder service
@@ -459,11 +449,12 @@
             return mImpl.supportsLocalVoiceInteraction();
         }
 
-        void notifyActivityEventChanged() {
+        void notifyActivityDestroyed(@NonNull IBinder activityToken) {
             synchronized (this) {
-                if (mImpl == null) return;
+                if (mImpl == null || activityToken == null) return;
 
-                Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityDestroyedLocked(activityToken));
             }
         }
 
@@ -806,8 +797,10 @@
             if (TextUtils.isEmpty(curInteractor)) {
                 return null;
             }
-            if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+            if (DEBUG) {
+                Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
                     + " user=" + userHandle);
+            }
             return ComponentName.unflattenFromString(curInteractor);
         }
 
@@ -815,8 +808,9 @@
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                     Settings.Secure.VOICE_INTERACTION_SERVICE,
                     comp != null ? comp.flattenToShortString() : "", userHandle);
-            if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
-                    + " user=" + userHandle);
+            if (DEBUG) {
+                Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+            }
         }
 
         ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1233,6 +1227,16 @@
             }
         }
 
+        @Override
+        public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) {
+            synchronized (this) {
+                if (mImpl == null || activityToken == null) {
+                    return;
+                }
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityEventChangedLocked(activityToken, type));
+            }
+        }
         //----------------- Hotword Detection/Validation APIs --------------------------------//
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
@@ -1914,7 +1918,6 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
-                pw.println("  mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2028,6 +2031,11 @@
 
                 List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
 
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+                            roleHolders);
+                }
+
                 // TODO(b/226201975): this method is beling called when a pre-created user is added,
                 // at which point it doesn't have any role holders. But it's not called again when
                 // the actual user is added (i.e., when the  pre-created user is converted), so we
@@ -2038,9 +2046,9 @@
                 if (roleHolders.isEmpty()) {
                     UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
                     if (userInfo != null && userInfo.preCreated) {
-                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
-                                userInfo.toFullString());
-                        mIgnoredPreCreatedUsers.add(user);
+                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+                                + " this method will be called again when it's converted to a real"
+                                + " user", userInfo.toFullString());
                         return;
                     }
                 }
@@ -2339,4 +2347,36 @@
             }
         };
     }
+
+    /**
+     * End the latency tracking log for keyphrase hotword invocation.
+     * The measurement covers from when the SoundTrigger HAL emits an event, captured in
+     * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging}
+     * to when the {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
+     */
+    private final IVoiceInteractionSessionListener mLatencyLoggingListener =
+            new IVoiceInteractionSessionListener.Stub() {
+                @Override
+                public void onVoiceSessionShown() throws RemoteException {}
+
+                @Override
+                public void onVoiceSessionHidden() throws RemoteException {}
+
+                @Override
+                public void onVoiceSessionWindowVisibilityChanged(boolean visible)
+                        throws RemoteException {
+                    if (visible) {
+                        LatencyTracker.getInstance(mContext)
+                                .onActionEnd(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+                    }
+                }
+
+                @Override
+                public void onSetUiHints(Bundle args) throws RemoteException {}
+
+                @Override
+                public IBinder asBinder() {
+                    return mServiceStub;
+                }
+            };
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 9f66059..dcf7b78 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -522,9 +522,23 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    public void notifyActivityEventChangedLocked() {
+    public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (mActiveSession == null || !mActiveSession.mShown) {
+            if (DEBUG) {
+                Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
+                        + " hidden session");
+            }
+            return;
+        }
+        mActiveSession.notifyActivityDestroyedLocked(activityToken);
+    }
+
+    public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
         }
         if (mActiveSession == null || !mActiveSession.mShown) {
             if (DEBUG) {
@@ -533,7 +547,7 @@
             }
             return;
         }
-        mActiveSession.notifyActivityEventChangedLocked();
+        mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
     public void updateStateLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 980b3b5..f8bc499 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -61,6 +61,7 @@
 import android.service.voice.VisibleActivityInfo;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IWindowManager;
 
@@ -130,7 +131,11 @@
     private boolean mListeningVisibleActivity;
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
-    private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+    // Records the visible activity information the system has already called onVisible, without
+    // confirming the result of callback. When activity visible state is changed, we use this to
+    // determine to call onVisible or onInvisible to assistant application.
+    private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken =
+            new ArrayMap<>();
     private final PowerManagerInternal mPowerManagerInternal;
     private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
     private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
@@ -535,7 +540,7 @@
 
     public void cancelLocked(boolean finishTask) {
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
         hideLocked();
         mCanceled = true;
         if (mBound) {
@@ -613,17 +618,24 @@
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
         }
-        mListeningVisibleActivity = true;
-        mVisibleActivityInfos.clear();
 
-        mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening");
-            }
-            synchronized (mLock) {
-                handleVisibleActivitiesLocked();
-            }
-        });
+        if (!mShown || mCanceled || mSession == null) {
+            return;
+        }
+
+        mListeningVisibleActivity = true;
+        mVisibleActivityInfoForToken.clear();
+
+        // It should only need to report which activities are visible
+        final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+
+        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+            return;
+        }
+        notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+                VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos);
     }
 
     void stopListeningVisibleActivityChangedLocked() {
@@ -631,12 +643,13 @@
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
         }
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
     }
 
-    void notifyActivityEventChangedLocked() {
+    void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
         if (!mListeningVisibleActivity) {
             if (DEBUG) {
@@ -645,99 +658,139 @@
             return;
         }
         mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event");
-            }
             synchronized (mLock) {
-                handleVisibleActivitiesLocked();
+                handleVisibleActivitiesLocked(activityToken, type);
             }
         });
     }
 
-    private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+    private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "getVisibleActivityInfosLocked");
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked");
         }
         List<ActivityAssistInfo> allVisibleActivities =
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .getTopVisibleActivities();
         if (DEBUG) {
-            Slog.d(TAG,
-                    "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities="
+                    + allVisibleActivities);
         }
-        if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+        if (allVisibleActivities.isEmpty()) {
             Slog.w(TAG, "no visible activity");
             return null;
         }
         final int count = allVisibleActivities.size();
-        final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap =
+                new ArrayMap<>(count);
         for (int i = 0; i < count; i++) {
             ActivityAssistInfo info = allVisibleActivities.get(i);
             if (DEBUG) {
-                Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+                Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken()
                         + ", assistToken=" + info.getAssistToken()
                         + ", taskId=" + info.getTaskId());
             }
-            visibleActivityInfos.add(
+            visibleActivityInfoArrayMap.put(info.getActivityToken(),
                     new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
         }
-        return visibleActivityInfos;
+        return visibleActivityInfoArrayMap;
     }
 
-    private void handleVisibleActivitiesLocked() {
+    // TODO(b/242359988): Split this method up
+    private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "handleVisibleActivitiesLocked");
+            Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
-        if (mSession == null) {
-            return;
-        }
-        if (!mShown || !mListeningVisibleActivity || mCanceled) {
-            return;
-        }
-        final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
 
-        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-            mVisibleActivityInfos.clear();
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
             return;
         }
-        if (mVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
-            mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+        if (!mShown || mCanceled || mSession == null) {
             return;
         }
 
-        final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
-        final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
+        // We use this local variable to determine to call onVisible or onInvisible.
+        boolean notifyOnVisible = false;
+        VisibleActivityInfo notifyVisibleActivityInfo = null;
 
-        removedActivities.addAll(mVisibleActivityInfos);
-        for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
-            final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
-            if (!removedActivities.isEmpty() && removedActivities.contains(
-                    candidateVisibleActivityInfo)) {
-                removedActivities.remove(candidateVisibleActivityInfo);
-            } else {
-                addedActivities.add(candidateVisibleActivityInfo);
+        if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START
+                || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) {
+            // It seems that the onStart is unnecessary. But if we have it, the assistant
+            // application can request the directActions early. Even if we have the onStart,
+            // we still need the onResume because it is possible that the activity goes to
+            // onResume from onPause with invisible before the activity goes to onStop from
+            // onPause.
+
+            // Check if we have reported this activity as visible. If we have reported it as
+            // visible, do nothing.
+            if (mVisibleActivityInfoForToken.containsKey(activityToken)) {
+                return;
+            }
+
+            // Before reporting this activity as visible, we need to make sure the activity
+            // is really visible.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+            notifyOnVisible = true;
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) {
+            // For the onPause stage, the Activity is not necessarily invisible now, so we need
+            // to check its state.
+            // Note: After syncing with Activity owner, before the onPause is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo != null) {
+                return;
+            }
+
+            // Also make sure we previously reported this Activity as visible.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) {
+            // For the onStop stage, the activity is in invisible state. We only need to consider if
+            // we have reported this activity as visible. If we have reported it as visible, we
+            // need to report it as invisible.
+            // Why we still need onStop? Because it is possible that the activity is in a visible
+            // state during onPause stage, when the activity enters onStop from onPause, we may
+            // need to notify onInvisible.
+            // Note: After syncing with Activity owner, before the onStop is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else {
+            Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type);
+            return;
+        }
+
+        try {
+            mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo,
+                    notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED
+                            : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+        } catch (RemoteException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e);
             }
         }
 
-        if (!addedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(addedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        if (notifyOnVisible) {
+            mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo);
+        } else {
+            mVisibleActivityInfoForToken.remove(activityToken);
         }
-        if (!removedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(removedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-        }
-
-        mVisibleActivityInfos.clear();
-        mVisibleActivityInfos.addAll(newVisibleActivityInfos);
     }
 
     private void notifyVisibleActivitiesChangedLocked(
-            List<VisibleActivityInfo> visibleActivityInfos, int type) {
+            ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) {
         if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
             return;
         }
@@ -746,7 +799,7 @@
         }
         try {
             for (int i = 0; i < visibleActivityInfos.size(); i++) {
-                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type);
+                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type);
             }
         } catch (RemoteException e) {
             if (DEBUG) {
@@ -759,6 +812,51 @@
         }
     }
 
+    private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity(
+            @NonNull IBinder activityToken) {
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+        if (visibleActivityInfos == null) {
+            return null;
+        }
+        return visibleActivityInfos.get(activityToken);
+    }
+
+    void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
+            return;
+        }
+        mScheduledExecutorService.execute(() -> {
+            synchronized (mLock) {
+                if (!mListeningVisibleActivity) {
+                    return;
+                }
+                if (!mShown || mCanceled || mSession == null) {
+                    return;
+                }
+
+                VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove(
+                        activityToken);
+                if (visibleActivityInfo != null) {
+                    try {
+                        mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo,
+                                VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+                    } catch (RemoteException e) {
+                        if (DEBUG) {
+                            Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
     private void removeFromLowPowerStandbyAllowlist() {
         synchronized (mLock) {
             if (mLowPowerStandbyAllowlisted) {
diff --git a/telephony/OWNERS b/telephony/OWNERS
index e0c5f8f..025869d 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -17,3 +17,6 @@
 per-file SubscriptionInfo.java=set noparent
 per-file SubscriptionInfo.java=jackyu@google.com,amruthr@google.com
 
+# Requiring TL ownership for new carrier config keys.
+per-file CarrierConfigManager.java=set noparent
+per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 4230225..f848c40 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -190,12 +190,11 @@
     }
 
     /**
-     * Returns the userId of the Context object, if called from a system app,
+     * Returns the userId of the current process, if called from a system app,
      * otherwise it returns the caller's userId
-     * @param context The context object passed in by the caller.
-     * @return
+     * @return userId of the caller.
      */
-    private static int getIncomingUserId(Context context) {
+    private static int getIncomingUserId() {
         int contextUserId = UserHandle.myUserId();
         final int callingUid = Binder.getCallingUid();
         if (DEBUG_MULTIUSER) {
@@ -231,7 +230,7 @@
      */
     @UnsupportedAppUsage
     public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
-        return getApplicationCollectionAsUser(context, getIncomingUserId(context));
+        return getApplicationCollectionAsUser(context, getIncomingUserId());
     }
 
     /**
@@ -590,7 +589,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static void setDefaultApplication(String packageName, Context context) {
-        setDefaultApplicationAsUser(packageName, context, getIncomingUserId(context));
+        setDefaultApplicationAsUser(packageName, context, getIncomingUserId());
     }
 
     /**
@@ -952,7 +951,7 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
-        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId(context));
+        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
     }
 
     /**
@@ -988,7 +987,18 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
-        int userId = getIncomingUserId(context);
+        return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+    }
+
+    /**
+     * Gets the default MMS application on a given user
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @param userId target user ID.
+     * @return component name of the app and class to deliver MMS messages to.
+     */
+    public static ComponentName getDefaultMmsApplicationAsUser(Context context,
+            boolean updateIfNeeded, int userId) {
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1013,7 +1023,19 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static ComponentName getDefaultRespondViaMessageApplication(Context context,
             boolean updateIfNeeded) {
-        int userId = getIncomingUserId(context);
+        return getDefaultRespondViaMessageApplicationAsUser(context, updateIfNeeded,
+                getIncomingUserId());
+    }
+
+    /**
+     * Gets the default Respond Via Message application on a given user
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured
+     * @param userId target user ID.
+     * @return component name of the app and class to direct Respond Via Message intent to
+     */
+    public static ComponentName getDefaultRespondViaMessageApplicationAsUser(Context context,
+            boolean updateIfNeeded, int userId) {
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1039,7 +1061,7 @@
      */
     public static ComponentName getDefaultSendToApplication(Context context,
             boolean updateIfNeeded) {
-        int userId = getIncomingUserId(context);
+        int userId = getIncomingUserId();
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1064,7 +1086,20 @@
      */
     public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
             Context context, boolean updateIfNeeded) {
-        int userId = getIncomingUserId(context);
+        return getDefaultExternalTelephonyProviderChangedApplicationAsUser(context, updateIfNeeded,
+                getIncomingUserId());
+    }
+
+    /**
+     * Gets the default application that handles external changes to the SmsProvider and
+     * MmsProvider on a given user.
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured
+     * @param userId target user ID.
+     * @return component name of the app and class to deliver change intents to.
+     */
+    public static ComponentName getDefaultExternalTelephonyProviderChangedApplicationAsUser(
+            Context context, boolean updateIfNeeded, int userId) {
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1089,7 +1124,18 @@
      */
     public static ComponentName getDefaultSimFullApplication(
             Context context, boolean updateIfNeeded) {
-        int userId = getIncomingUserId(context);
+        return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+    }
+
+    /**
+     * Gets the default application that handles sim full event on a given user.
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured
+     * @param userId target user ID.
+     * @return component name of the app and class to deliver change intents to
+     */
+    public static ComponentName getDefaultSimFullApplicationAsUser(Context context,
+            boolean updateIfNeeded, int userId) {
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1107,13 +1153,19 @@
     }
 
     /**
-     * Returns whether need to write the SMS message to SMS database for this package.
+     * Returns whether need to wrgetIncomingUserIdite the SMS message to SMS database for this
+     * package.
      * <p>
      * Caller must pass in the correct user context if calling from a singleton service.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
-        return !isDefaultSmsApplication(context, packageName);
+        return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserId());
+    }
+
+    public static boolean shouldWriteMessageForPackageAsUser(String packageName, Context context,
+            int userId) {
+        return !isDefaultSmsApplicationAsUser(context, packageName, userId);
     }
 
     /**
@@ -1125,28 +1177,42 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultSmsApplication(Context context, String packageName) {
+        return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserId());
+    }
+
+    /**
+     * Check if a package is default sms app (or equivalent, like bluetooth) on a given user.
+     *
+     * @param context context from the calling app
+     * @param packageName the name of the package to be checked
+     * @param userId target user ID.
+     * @return true if the package is default sms app or bluetooth
+     */
+    public static boolean isDefaultSmsApplicationAsUser(Context context, String packageName,
+            int userId) {
         if (packageName == null) {
             return false;
         }
-        final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
-        final String bluetoothPackageName = context.getResources()
+        ComponentName component = getDefaultSmsApplicationAsUser(context, false,
+                userId);
+        if (component == null) {
+            return false;
+        }
+
+        String defaultSmsPackage = component.getPackageName();
+        if (defaultSmsPackage == null) {
+            return false;
+        }
+
+        String bluetoothPackageName = context.getResources()
                 .getString(com.android.internal.R.string.config_systemBluetoothStack);
 
-        if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
-                || bluetoothPackageName.equals(packageName)) {
+        if (defaultSmsPackage.equals(packageName) || bluetoothPackageName.equals(packageName)) {
             return true;
         }
         return false;
     }
 
-    private static String getDefaultSmsApplicationPackageName(Context context) {
-        final ComponentName component = getDefaultSmsApplication(context, false);
-        if (component != null) {
-            return component.getPackageName();
-        }
-        return null;
-    }
-
     /**
      * Check if a package is default mms app (or equivalent, like bluetooth)
      *
@@ -1156,25 +1222,40 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultMmsApplication(Context context, String packageName) {
+        return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserId());
+    }
+
+    /**
+     * Check if a package is default mms app (or equivalent, like bluetooth) on a given user.
+     *
+     * @param context context from the calling app
+     * @param packageName the name of the package to be checked
+     * @param userId target user ID.
+     * @return true if the package is default mms app or bluetooth
+     */
+    public static boolean isDefaultMmsApplicationAsUser(Context context, String packageName,
+            int userId) {
         if (packageName == null) {
             return false;
         }
-        String defaultMmsPackage = getDefaultMmsApplicationPackageName(context);
+
+        ComponentName component = getDefaultMmsApplicationAsUser(context, false,
+                userId);
+        if (component == null) {
+            return false;
+        }
+
+        String defaultMmsPackage = component.getPackageName();
+        if (defaultMmsPackage == null) {
+            return false;
+        }
+
         String bluetoothPackageName = context.getResources()
                 .getString(com.android.internal.R.string.config_systemBluetoothStack);
 
-        if ((defaultMmsPackage != null && defaultMmsPackage.equals(packageName))
-                || bluetoothPackageName.equals(packageName)) {
+        if (defaultMmsPackage.equals(packageName)|| bluetoothPackageName.equals(packageName)) {
             return true;
         }
         return false;
     }
-
-    private static String getDefaultMmsApplicationPackageName(Context context) {
-        ComponentName component = getDefaultMmsApplication(context, false);
-        if (component != null) {
-            return component.getPackageName();
-        }
-        return null;
-    }
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 7eec86a..0fdf40d 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -23,6 +23,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
 
 /**
  * Contains access network related constants.
@@ -114,7 +115,7 @@
 
         /** @hide */
         public static @RadioAccessNetworkType int fromString(@NonNull String str) {
-            switch (str.toUpperCase()) {
+            switch (str.toUpperCase(Locale.ROOT)) {
                 case "UNKNOWN": return UNKNOWN;
                 case "GERAN": return GERAN;
                 case "UTRAN": return UTRAN;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b6944eb..2a1efed 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -52,7 +52,9 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Provides access to telephony configuration values that are carrier-specific.
@@ -8647,6 +8649,73 @@
             "unthrottle_data_retry_when_tac_changes_bool";
 
     /**
+     * A list of premium capabilities the carrier supports. Applications can prompt users to
+     * purchase these premium capabilities from their carrier for a network boost.
+     * Valid values are any of {@link TelephonyManager.PremiumCapability}.
+     *
+     * This is empty by default, indicating that no premium capabilities are supported.
+     *
+     * @see TelephonyManager#isPremiumCapabilityAvailableForPurchase(int)
+     * @see TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)
+     */
+    public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY =
+            "supported_premium_capabilities_int_array";
+
+    /**
+     * The amount of time in milliseconds the notification for a network boost via
+     * premium capabilities will be visible to the user after
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * requests user action to purchase the boost from the carrier. Once the timeout expires,
+     * the booster notification will be automatically dismissed and the request will fail with
+     * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT}.
+     *
+     * The default value is 30 minutes.
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG =
+            "premium_capability_notification_display_timeout_millis_long";
+
+    /**
+     * The amount of time in milliseconds that the notification for a network boost via
+     * premium capabilities should be blocked when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns a failure due to user action or timeout.
+     *
+     * The default value is 30 minutes.
+     *
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     */
+    public static final String
+            KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
+            "premium_capability_notification_backoff_hysteresis_time_millis_long";
+
+    /**
+     * The amount of time in milliseconds that the purchase request should be throttled when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns a failure due to the carrier.
+     *
+     * The default value is 30 minutes.
+     *
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED
+     */
+    public static final String
+            KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
+            "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
+
+    /**
+     * The URL to redirect to when the user clicks on the notification for a network boost via
+     * premium capabilities after applications call
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
+     * If the URL is empty or invalid, the purchase request will return
+     * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED}.
+     *
+     * This is empty by default.
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING =
+            "premium_capability_purchase_url_string";
+
+    /**
      * IWLAN handover rules that determine whether handover is allowed or disallowed between
      * cellular and IWLAN.
      *
@@ -9312,6 +9381,15 @@
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+        sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[]{});
+        sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
+                TimeUnit.MINUTES.toMillis(30));
+        sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
+                TimeUnit.MINUTES.toMillis(30));
+        sDefaults.putLong(
+                KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
+                TimeUnit.MINUTES.toMillis(30));
+        sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
                 "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                         + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index c138018..ceea94b 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -27,6 +27,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -276,8 +277,8 @@
         if (str.length() != pattern.length()) {
             return false;
         }
-        String lowerCaseStr = str.toLowerCase();
-        String lowerCasePattern = pattern.toLowerCase();
+        String lowerCaseStr = str.toLowerCase(Locale.ROOT);
+        String lowerCasePattern = pattern.toLowerCase(Locale.ROOT);
 
         for (int i = 0; i < lowerCasePattern.length(); i++) {
             if (lowerCasePattern.charAt(i) != lowerCaseStr.charAt(i)
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 1d6798b..a0467c2 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -856,6 +856,31 @@
         public Builder() {}
 
         /**
+         * Builder from the existing {@link NetworkRegistrationInfo}.
+         *
+         * @param nri The network registration info object.
+         * @hide
+         */
+        public Builder(@NonNull NetworkRegistrationInfo nri) {
+            mDomain = nri.mDomain;
+            mTransportType = nri.mTransportType;
+            mInitialRegistrationState = nri.mInitialRegistrationState;
+            mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
+            mRejectCause = nri.mRejectCause;
+            mEmergencyOnly = nri.mEmergencyOnly;
+            mAvailableServices = new ArrayList<>(nri.mAvailableServices);
+            mCellIdentity = nri.mCellIdentity;
+            if (nri.mDataSpecificInfo != null) {
+                mDataSpecificRegistrationInfo = new DataSpecificRegistrationInfo(
+                        nri.mDataSpecificInfo);
+            }
+            if (nri.mVoiceSpecificInfo != null) {
+                mVoiceSpecificRegistrationInfo = new VoiceSpecificRegistrationInfo(
+                        nri.mVoiceSpecificInfo);
+            }
+        }
+
+        /**
          * Set the network domain.
          *
          * @param domain Network domain.
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index c75de42..ac892da 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -265,7 +265,7 @@
     /** @hide */
     @Override
     public void onDestroy() {
-        mHandlerThread.quit();
+        mHandlerThread.quitSafely();
         super.onDestroy();
     }
 
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index dfa4fc0..b9008c4 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
 import android.os.PersistableBundle;
 import android.provider.Contacts;
 import android.provider.ContactsContract;
@@ -2901,7 +2900,7 @@
         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
         PhoneNumber n1;
         PhoneNumber n2;
-        defaultCountryIso = defaultCountryIso.toUpperCase();
+        defaultCountryIso = defaultCountryIso.toUpperCase(Locale.ROOT);
         try {
             n1 = util.parseAndKeepRawInput(number1, defaultCountryIso);
             n2 = util.parseAndKeepRawInput(number2, defaultCountryIso);
diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index f1e9011..90d6f89 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -24,6 +24,8 @@
 
 import com.android.internal.telephony.RILConstants;
 
+import java.util.Locale;
+
 
 /**
  * Object to indicate the phone radio type and access technology.
@@ -367,7 +369,7 @@
     }
 
     public static int rafTypeFromString(String rafList) {
-        rafList = rafList.toUpperCase();
+        rafList = rafList.toUpperCase(Locale.ROOT);
         String[] rafs = rafList.split("\\|");
         int result = 0;
         for(String raf : rafs) {
diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java
index 3c18245..7053b44 100644
--- a/telephony/java/android/telephony/SignalThresholdInfo.java
+++ b/telephony/java/android/telephony/SignalThresholdInfo.java
@@ -100,24 +100,34 @@
      */
     public static final int SIGNAL_MEASUREMENT_TYPE_SSSINR = 8;
 
-    /** @hide */
-    @IntDef(prefix = {"SIGNAL_MEASUREMENT_TYPE_"}, value = {
-            SIGNAL_MEASUREMENT_TYPE_UNKNOWN,
-            SIGNAL_MEASUREMENT_TYPE_RSSI,
-            SIGNAL_MEASUREMENT_TYPE_RSCP,
-            SIGNAL_MEASUREMENT_TYPE_RSRP,
-            SIGNAL_MEASUREMENT_TYPE_RSRQ,
-            SIGNAL_MEASUREMENT_TYPE_RSSNR,
-            SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-            SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-            SIGNAL_MEASUREMENT_TYPE_SSSINR
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SignalMeasurementType {
-    }
+    /**
+     * The ratio between the received energy from the pilot signal CPICH per chip (Ec) to the
+     * noise density (No).
+     * Range: -24 dBm to 1 dBm.
+     * Used RAN: {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+     * Reference: 3GPP TS 25.215 5.1.5
+     */
+    public static final int SIGNAL_MEASUREMENT_TYPE_ECNO = 9;
 
-    @SignalMeasurementType
-    private final int mSignalMeasurementType;
+    /** @hide */
+    @IntDef(
+            prefix = {"SIGNAL_MEASUREMENT_TYPE_"},
+            value = {
+                SIGNAL_MEASUREMENT_TYPE_UNKNOWN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                SIGNAL_MEASUREMENT_TYPE_RSCP,
+                SIGNAL_MEASUREMENT_TYPE_RSRP,
+                SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                SIGNAL_MEASUREMENT_TYPE_RSSNR,
+                SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                SIGNAL_MEASUREMENT_TYPE_SSSINR,
+                SIGNAL_MEASUREMENT_TYPE_ECNO
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SignalMeasurementType {}
+
+    @SignalMeasurementType private final int mSignalMeasurementType;
 
     /**
      * A hysteresis time in milliseconds to prevent flapping.
@@ -149,8 +159,7 @@
     /**
      * The radio access network type associated with the signal thresholds.
      */
-    @AccessNetworkConstants.RadioAccessNetworkType
-    private final int mRan;
+    @AccessNetworkConstants.RadioAccessNetworkType private final int mRan;
 
     /**
      * Indicates the hysteresisMs is disabled.
@@ -166,7 +175,6 @@
      */
     public static final int HYSTERESIS_DB_DISABLED = 0;
 
-
     /**
      * Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_RSSI}.
      *
@@ -280,6 +288,20 @@
     public static final int SIGNAL_SSSINR_MAX_VALUE = 40;
 
     /**
+     * Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_ECNO}.
+     *
+     * @hide
+     */
+    public static final int SIGNAL_ECNO_MIN_VALUE = -24;
+
+    /**
+     * Maximum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_ECNO}.
+     *
+     * @hide
+     */
+    public static final int SIGNAL_ECNO_MAX_VALUE = 1;
+
+    /**
      * The minimum number of thresholds allowed in each SignalThresholdInfo.
      *
      * @hide
@@ -303,9 +325,13 @@
      * @param thresholds        threshold value
      * @param isEnabled         isEnabled
      */
-    private SignalThresholdInfo(@AccessNetworkConstants.RadioAccessNetworkType int ran,
-            @SignalMeasurementType int signalMeasurementType, int hysteresisMs, int hysteresisDb,
-            @NonNull int[] thresholds, boolean isEnabled) {
+    private SignalThresholdInfo(
+            @AccessNetworkConstants.RadioAccessNetworkType int ran,
+            @SignalMeasurementType int signalMeasurementType,
+            int hysteresisMs,
+            int hysteresisDb,
+            @NonNull int[] thresholds,
+            boolean isEnabled) {
         Objects.requireNonNull(thresholds, "thresholds must not be null");
         validateRanWithMeasurementType(ran, signalMeasurementType);
         validateThresholdRange(signalMeasurementType, thresholds);
@@ -399,6 +425,7 @@
          * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRP
          * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRQ
          * @see #SIGNAL_MEASUREMENT_TYPE_SSSINR
+         * @see #SIGNAL_MEASUREMENT_TYPE_ECNO
          * @see #getThresholds() for more details on signal strength thresholds
          */
         public @NonNull Builder setThresholds(@NonNull int[] thresholds) {
@@ -417,18 +444,20 @@
          */
         public @NonNull Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) {
             Objects.requireNonNull(thresholds, "thresholds must not be null");
-            if (!isSystem && (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
-                    || thresholds.length > MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED)) {
+            if (!isSystem
+                    && (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
+                            || thresholds.length > MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED)) {
                 throw new IllegalArgumentException(
-                        "thresholds length must between " + MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
-                                + " and " + MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED);
+                        "thresholds length must between "
+                                + MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
+                                + " and "
+                                + MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED);
             }
             mThresholds = thresholds.clone();
             Arrays.sort(mThresholds);
             return this;
         }
 
-
         /**
          * Set if the modem should trigger the report based on the criteria.
          *
@@ -451,8 +480,13 @@
          * measurement type
          */
         public @NonNull SignalThresholdInfo build() {
-            return new SignalThresholdInfo(mRan, mSignalMeasurementType, mHysteresisMs,
-                    mHysteresisDb, mThresholds, mIsEnabled);
+            return new SignalThresholdInfo(
+                    mRan,
+                    mSignalMeasurementType,
+                    mHysteresisMs,
+                    mHysteresisDb,
+                    mThresholds,
+                    mIsEnabled);
         }
     }
 
@@ -508,6 +542,7 @@
      * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRP
      * @see #SIGNAL_MEASUREMENT_TYPE_SSRSRQ
      * @see #SIGNAL_MEASUREMENT_TYPE_SSSINR
+     * @see #SIGNAL_MEASUREMENT_TYPE_ECNO
      */
     public @NonNull int[] getThresholds() {
         return mThresholds.clone();
@@ -574,8 +609,13 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mRan, mSignalMeasurementType, mHysteresisMs, mHysteresisDb,
-                Arrays.hashCode(mThresholds), mIsEnabled);
+        return Objects.hash(
+                mRan,
+                mSignalMeasurementType,
+                mHysteresisMs,
+                mHysteresisDb,
+                Arrays.hashCode(mThresholds),
+                mIsEnabled);
     }
 
     public static final @NonNull Parcelable.Creator<SignalThresholdInfo> CREATOR =
@@ -594,13 +634,20 @@
     @Override
     public String toString() {
         return new StringBuilder("SignalThresholdInfo{")
-                .append("mRan=").append(mRan)
-                .append(" mSignalMeasurementType=").append(mSignalMeasurementType)
-                .append(" mHysteresisMs=").append(mHysteresisMs)
-                .append(" mHysteresisDb=").append(mHysteresisDb)
-                .append(" mThresholds=").append(Arrays.toString(mThresholds))
-                .append(" mIsEnabled=").append(mIsEnabled)
-                .append("}").toString();
+                .append("mRan=")
+                .append(mRan)
+                .append(" mSignalMeasurementType=")
+                .append(mSignalMeasurementType)
+                .append(" mHysteresisMs=")
+                .append(mHysteresisMs)
+                .append(" mHysteresisDb=")
+                .append(mHysteresisDb)
+                .append(" mThresholds=")
+                .append(Arrays.toString(mThresholds))
+                .append(" mIsEnabled=")
+                .append(mIsEnabled)
+                .append("}")
+                .toString();
     }
 
     /**
@@ -624,6 +671,8 @@
                 return threshold >= SIGNAL_SSRSRQ_MIN_VALUE && threshold <= SIGNAL_SSRSRQ_MAX_VALUE;
             case SIGNAL_MEASUREMENT_TYPE_SSSINR:
                 return threshold >= SIGNAL_SSSINR_MIN_VALUE && threshold <= SIGNAL_SSSINR_MAX_VALUE;
+            case SIGNAL_MEASUREMENT_TYPE_ECNO:
+                return threshold >= SIGNAL_ECNO_MIN_VALUE && threshold <= SIGNAL_ECNO_MAX_VALUE;
             default:
                 return false;
         }
@@ -640,6 +689,7 @@
                 return ran == AccessNetworkConstants.AccessNetworkType.GERAN
                         || ran == AccessNetworkConstants.AccessNetworkType.CDMA2000;
             case SIGNAL_MEASUREMENT_TYPE_RSCP:
+            case SIGNAL_MEASUREMENT_TYPE_ECNO:
                 return ran == AccessNetworkConstants.AccessNetworkType.UTRAN;
             case SIGNAL_MEASUREMENT_TYPE_RSRP:
             case SIGNAL_MEASUREMENT_TYPE_RSRQ:
@@ -663,13 +713,15 @@
         }
     }
 
-    private void validateThresholdRange(@SignalMeasurementType int signalMeasurement,
-            int[] thresholds) {
+    private void validateThresholdRange(
+            @SignalMeasurementType int signalMeasurement, int[] thresholds) {
         for (int threshold : thresholds) {
             if (!isValidThreshold(signalMeasurement, threshold)) {
                 throw new IllegalArgumentException(
-                        "invalid signal measurement type: " + signalMeasurement
-                                + " with threshold: " + threshold);
+                        "invalid signal measurement type: "
+                                + signalMeasurement
+                                + " with threshold: "
+                                + threshold);
             }
         }
     }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index cb985bf..eb96d37 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -18,6 +18,7 @@
 
 import static android.text.TextUtils.formatSimple;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +38,9 @@
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager.ProfileClass;
+import android.telephony.SubscriptionManager.SimDisplayNameSource;
+import android.telephony.SubscriptionManager.SubscriptionType;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -55,7 +59,6 @@
  * A Parcelable class for Subscription Information.
  */
 public class SubscriptionInfo implements Parcelable {
-
     /**
      * Size of text to render on the icon.
      */
@@ -65,162 +68,180 @@
      * Subscription Identifier, this is a device unique number
      * and not an index into an array
      */
-    private int mId;
+    private final int mId;
 
     /**
-     * The GID for a SIM that maybe associated with this subscription, empty if unknown
+     * The ICCID of the SIM that is associated with this subscription, empty if unknown.
      */
-    private String mIccId;
+    @NonNull
+    private final String mIccId;
 
     /**
-     * The index of the slot that currently contains the subscription
-     * and not necessarily unique and maybe INVALID_SLOT_ID if unknown
+     * The index of the SIM slot that currently contains the subscription and not necessarily unique
+     * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription
+     * is inactive.
      */
-    private int mSimSlotIndex;
+    private final int mSimSlotIndex;
 
     /**
-     * The name displayed to the user that identifies this subscription
+     * The name displayed to the user that identifies this subscription. This name is used
+     * in Settings page and can be renamed by the user.
      */
-    private CharSequence mDisplayName;
+    @NonNull
+    private final CharSequence mDisplayName;
 
     /**
-     * String that identifies SPN/PLMN
-     * TODO : Add a new field that identifies only SPN for a sim
+     * The name displayed to the user that identifies subscription provider name. This name is the
+     * SPN displayed in status bar and many other places. Can't be renamed by the user.
      */
-    private CharSequence mCarrierName;
+    @NonNull
+    private final CharSequence mCarrierName;
 
     /**
      * The subscription carrier id.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
-    private int mCarrierId;
+    private final int mCarrierId;
 
     /**
-     * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN,
-     * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT.
+     * The source of the {@link #mCarrierName}.
      */
-    private int mNameSource;
+    @SimDisplayNameSource
+    private final int mNameSource;
 
     /**
-     * The color to be used for tinting the icon when displaying to the user
+     * The color to be used for tinting the icon when displaying to the user.
      */
-    private int mIconTint;
+    private final int mIconTint;
 
     /**
-     * A number presented to the user identify this subscription
+     * The number presented to the user identify this subscription.
      */
-    private String mNumber;
+    @NonNull
+    private final String mNumber;
 
     /**
-     * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
-    private int mDataRoaming;
+    private final int mDataRoaming;
 
     /**
-     * SIM icon bitmap cache
-     */
-    @Nullable private Bitmap mIconBitmap;
-
-    /**
-     * Mobile Country Code
-     */
-    private String mMcc;
-
-    /**
-     * Mobile Network Code
-     */
-    private String mMnc;
-
-    /**
-     * EHPLMNs associated with the subscription
-     */
-    private String[] mEhplmns;
-
-    /**
-     * HPLMNs associated with the subscription
-     */
-    private String[] mHplmns;
-
-    /**
-     * ISO Country code for the subscription's provider
-     */
-    private String mCountryIso;
-
-    /**
-     * Whether the subscription is an embedded one.
-     */
-    private boolean mIsEmbedded;
-
-    /**
-     * The access rules for this subscription, if it is embedded and defines any.
-     * This does not include access rules for non-embedded subscriptions.
+     * SIM icon bitmap cache.
      */
     @Nullable
-    private UiccAccessRule[] mNativeAccessRules;
+    private Bitmap mIconBitmap;
+
+    /**
+     * Mobile Country Code.
+     */
+    @Nullable
+    private final String mMcc;
+
+    /**
+     * Mobile Network Code.
+     */
+    @Nullable
+    private final String mMnc;
+
+    /**
+     * EHPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mEhplmns;
+
+    /**
+     * HPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mHplmns;
+
+    /**
+     * ISO Country code for the subscription's provider.
+     */
+    @NonNull
+    private final String mCountryIso;
+
+    /**
+     * Whether the subscription is from eSIM.
+     */
+    private final boolean mIsEmbedded;
+
+    /**
+     * The access rules for this subscription, if it is embedded and defines any. This does not
+     * include access rules for non-embedded subscriptions.
+     */
+    @Nullable
+    private final UiccAccessRule[] mNativeAccessRules;
 
     /**
      * The carrier certificates for this subscription that are saved in carrier configs.
      * This does not include access rules from the Uicc, whether embedded or non-embedded.
      */
     @Nullable
-    private UiccAccessRule[] mCarrierConfigAccessRules;
+    private final UiccAccessRule[] mCarrierConfigAccessRules;
 
     /**
      * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the
      * EID for an eUICC card.
      */
-    private String mCardString;
+    @NonNull
+    private final String mCardString;
 
     /**
-     * The card ID of the SIM card. This maps uniquely to the card string.
+     * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
      */
-    private int mCardId;
+    private final int mCardId;
 
     /**
      * Whether the subscription is opportunistic.
      */
-    private boolean mIsOpportunistic;
+    private final boolean mIsOpportunistic;
 
     /**
-     * A UUID assigned to the subscription group. It returns null if not assigned.
-     * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details.
+     * A UUID assigned to the subscription group. {@code null} if not assigned.
+     *
+     * @see SubscriptionManager#createSubscriptionGroup(List)
      */
     @Nullable
-    private ParcelUuid mGroupUUID;
+    private final ParcelUuid mGroupUuid;
 
     /**
-     * A package name that specifies who created the group. Null if mGroupUUID is null.
+     * A package name that specifies who created the group. Empty if not available.
      */
-    private String mGroupOwner;
+    @NonNull
+    private final String mGroupOwner;
 
     /**
-     * Whether group of the subscription is disabled.
-     * This is only useful if it's a grouped opportunistic subscription. In this case, if all
-     * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM
-     * or deactivated eSIM profile), we should disable this opportunistic subscription.
+     * Whether group of the subscription is disabled. This is only useful if it's a grouped
+     * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions
+     * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable
+     * this opportunistic subscription.
      */
-    private boolean mIsGroupDisabled = false;
+    private final boolean mIsGroupDisabled;
 
     /**
-     * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
-     * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
-     * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
-     * The profile class will be populated from the profile metadata if present. Otherwise,
-     * the profile class defaults to unset if there is no profile metadata or the subscription
-     * is not on an eUICC ({@link #isEmbedded} returns false).
+     * The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+     * {@code false}).
      */
-    private int mProfileClass;
+    @ProfileClass
+    private final int mProfileClass;
 
     /**
-     * Type of subscription
+     * Type of the subscription.
      */
-    private int mSubscriptionType;
+    @SubscriptionType
+    private final int mType;
 
     /**
      * Whether uicc applications are configured to enable or disable.
      * By default it's true.
      */
-    private boolean mAreUiccApplicationsEnabled = true;
+    private final boolean mAreUiccApplicationsEnabled;
 
     /**
      * The port index of the Uicc card.
@@ -230,25 +251,16 @@
     /**
      * Subscription's preferred usage setting.
      */
-    private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
-
-    /**
-     * Public copy constructor.
-     * @hide
-     */
-    public SubscriptionInfo(SubscriptionInfo info) {
-        this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName,
-                info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap,
-                info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules,
-                info.mCardString, info.mCardId, info.mIsOpportunistic,
-                info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled,
-                info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner,
-                info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled);
-    }
+    @UsageSetting
+    private final int mUsageSetting;
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -262,7 +274,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -276,7 +292,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -293,7 +313,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -311,49 +335,94 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
-            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled,
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
             @Nullable UiccAccessRule[] carrierConfigAccessRules,
             boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
-        this.mDisplayName = displayName;
+        this.mDisplayName =  displayName;
         this.mCarrierName = carrierName;
         this.mNameSource = nameSource;
         this.mIconTint = iconTint;
         this.mNumber = number;
         this.mDataRoaming = roaming;
         this.mIconBitmap = icon;
-        this.mMcc = mcc;
-        this.mMnc = mnc;
-        this.mCountryIso = countryIso;
+        this.mMcc = TextUtils.emptyIfNull(mcc);
+        this.mMnc = TextUtils.emptyIfNull(mnc);
+        this.mHplmns = null;
+        this.mEhplmns = null;
+        this.mCountryIso = TextUtils.emptyIfNull(countryIso);
         this.mIsEmbedded = isEmbedded;
         this.mNativeAccessRules = nativeAccessRules;
-        this.mCardString = cardString;
+        this.mCardString = TextUtils.emptyIfNull(cardString);
         this.mCardId = cardId;
         this.mIsOpportunistic = isOpportunistic;
-        this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID);
+        this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
         this.mIsGroupDisabled = isGroupDisabled;
         this.mCarrierId = carrierId;
         this.mProfileClass = profileClass;
-        this.mSubscriptionType = subType;
-        this.mGroupOwner = groupOwner;
+        this.mType = subType;
+        this.mGroupOwner = TextUtils.emptyIfNull(groupOwner);
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
         this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
         this.mPortIndex = portIndex;
         this.mUsageSetting = usageSetting;
     }
+
     /**
-     * @return the subscription ID.
+     * Constructor from builder.
+     *
+     * @param builder Builder of {@link SubscriptionInfo}.
+     */
+    private SubscriptionInfo(@NonNull Builder builder) {
+        this.mId = builder.mId;
+        this.mIccId = builder.mIccId;
+        this.mSimSlotIndex = builder.mSimSlotIndex;
+        this.mDisplayName = builder.mDisplayName;
+        this.mCarrierName = builder.mCarrierName;
+        this.mNameSource = builder.mNameSource;
+        this.mIconTint = builder.mIconTint;
+        this.mNumber = builder.mNumber;
+        this.mDataRoaming = builder.mDataRoaming;
+        this.mIconBitmap = builder.mIconBitmap;
+        this.mMcc = builder.mMcc;
+        this.mMnc = builder.mMnc;
+        this.mEhplmns = builder.mEhplmns;
+        this.mHplmns = builder.mHplmns;
+        this.mCountryIso = builder.mCountryIso;
+        this.mIsEmbedded = builder.mIsEmbedded;
+        this.mNativeAccessRules = builder.mNativeAccessRules;
+        this.mCardString = builder.mCardString;
+        this.mCardId = builder.mCardId;
+        this.mIsOpportunistic = builder.mIsOpportunistic;
+        this.mGroupUuid = builder.mGroupUuid;
+        this.mIsGroupDisabled = builder.mIsGroupDisabled;
+        this.mCarrierId = builder.mCarrierId;
+        this.mProfileClass = builder.mProfileClass;
+        this.mType = builder.mType;
+        this.mGroupOwner = builder.mGroupOwner;
+        this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules;
+        this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled;
+        this.mPortIndex = builder.mPortIndex;
+        this.mUsageSetting = builder.mUsageSetting;
+    }
+
+    /**
+     * @return The subscription ID.
      */
     public int getSubscriptionId() {
-        return this.mId;
+        return mId;
     }
 
     /**
@@ -370,78 +439,56 @@
      * @return the ICC ID, or an empty string if one of these requirements is not met
      */
     public String getIccId() {
-        return this.mIccId;
+        return mIccId;
     }
 
     /**
-     * @hide
-     */
-    public void clearIccId() {
-        this.mIccId = "";
-    }
-
-    /**
-     * @return the slot index of this Subscription's SIM card.
+     * @return The index of the SIM slot that currently contains the subscription and not
+     * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or
+     * the subscription is inactive.
      */
     public int getSimSlotIndex() {
-        return this.mSimSlotIndex;
+        return mSimSlotIndex;
     }
 
     /**
-     * @return the carrier id of this Subscription carrier.
+     * @return The carrier id of this subscription carrier.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
     public int getCarrierId() {
-        return this.mCarrierId;
+        return mCarrierId;
     }
 
     /**
-     * @return the name displayed to the user that identifies this subscription
+     * @return The name displayed to the user that identifies this subscription. This name is
+     * used in Settings page and can be renamed by the user.
+     *
+     * @see #getCarrierName()
      */
     public CharSequence getDisplayName() {
-        return this.mDisplayName;
+        return mDisplayName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setDisplayName(CharSequence name) {
-        this.mDisplayName = name;
-    }
-
-    /**
-     * @return the name displayed to the user that identifies Subscription provider name
+     * @return The name displayed to the user that identifies subscription provider name. This name
+     * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+     *
+     * @see #getDisplayName()
      */
     public CharSequence getCarrierName() {
-        return this.mCarrierName;
+        return mCarrierName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies Subscription provider name
-     * @hide
-     */
-    public void setCarrierName(CharSequence name) {
-        this.mCarrierName = name;
-    }
-
-    /**
-     * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or
-     * NAME_SOURCE_USER_INPUT.
+     * @return The source of the {@link #getCarrierName()}.
+     *
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SimDisplayNameSource
     public int getNameSource() {
-        return this.mNameSource;
-    }
-
-    /**
-     * @hide
-     */
-    public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) {
-        mEhplmns = ehplmns;
-        mHplmns = hplmns;
+        return mNameSource;
     }
 
     /**
@@ -499,15 +546,6 @@
     }
 
     /**
-     * Sets the color displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setIconTint(int iconTint) {
-        this.mIconTint = iconTint;
-    }
-
-    /**
      * Returns the number of this subscription.
      *
      * Starting with API level 30, returns the number of this subscription if the calling app meets
@@ -533,28 +571,23 @@
     }
 
     /**
-     * @hide
-     */
-    public void clearNumber() {
-        mNumber = "";
-    }
-
-    /**
-     * @return the data roaming state for this subscription, either
-     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
     public int getDataRoaming() {
-        return this.mDataRoaming;
+        return mDataRoaming;
     }
 
     /**
-     * @return the MCC.
+     * @return The mobile country code.
+     *
      * @deprecated Use {@link #getMccString()} instead.
      */
     @Deprecated
     public int getMcc() {
         try {
-            return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc);
+            return mMcc == null ? 0 : Integer.parseInt(mMcc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number");
             return 0;
@@ -562,13 +595,14 @@
     }
 
     /**
-     * @return the MNC.
+     * @return The mobile network code.
+     *
      * @deprecated Use {@link #getMncString()} instead.
      */
     @Deprecated
     public int getMnc() {
         try {
-            return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc);
+            return mMnc == null ? 0 : Integer.parseInt(mMnc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number");
             return 0;
@@ -576,36 +610,40 @@
     }
 
     /**
-     * @return The MCC, as a string.
+     * @return The mobile country code.
      */
-    public @Nullable String getMccString() {
-        return this.mMcc;
+    @Nullable
+    public String getMccString() {
+        return mMcc;
     }
 
     /**
-     * @return The MNC, as a string.
+     * @return The mobile network code.
      */
-    public @Nullable String getMncString() {
-        return this.mMnc;
+    @Nullable
+    public String getMncString() {
+        return mMnc;
     }
 
     /**
-     * @return the ISO country code
+     * @return The ISO country code. Empty if not available.
      */
     public String getCountryIso() {
-        return this.mCountryIso;
+        return mCountryIso;
     }
 
-    /** @return whether the subscription is an eUICC one. */
+    /**
+     * @return {@code true} if the subscription is from eSIM.
+     */
     public boolean isEmbedded() {
-        return this.mIsEmbedded;
+        return mIsEmbedded;
     }
 
     /**
      * An opportunistic subscription connects to a network that is
      * limited in functionality and / or coverage.
      *
-     * @return whether subscription is opportunistic.
+     * @return Whether subscription is opportunistic.
      */
     public boolean isOpportunistic() {
         return mIsOpportunistic;
@@ -617,23 +655,18 @@
      * Such that those subscriptions will have some affiliated behaviors such as opportunistic
      * subscription may be invisible to the user.
      *
-     * @return group UUID a String of group UUID if it belongs to a group. Otherwise
-     * it will return null.
+     * @return Group UUID a String of group UUID if it belongs to a group. Otherwise
+     * {@code null}.
      */
-    public @Nullable ParcelUuid getGroupUuid() {
-        return mGroupUUID;
+    @Nullable
+    public ParcelUuid getGroupUuid() {
+        return mGroupUuid;
     }
 
     /**
      * @hide
      */
-    public void clearGroupUuid() {
-        this.mGroupUUID = null;
-    }
-
-    /**
-     * @hide
-     */
+    @NonNull
     public List<String> getEhplmns() {
         return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns);
     }
@@ -641,36 +674,45 @@
     /**
      * @hide
      */
+    @NonNull
     public List<String> getHplmns() {
         return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns);
     }
 
     /**
-     * Return owner package of group the subscription belongs to.
+     * @return The owner package of group the subscription belongs to.
      *
      * @hide
      */
-    public @Nullable String getGroupOwner() {
+    @NonNull
+    public String getGroupOwner() {
         return mGroupOwner;
     }
 
     /**
-     * @return the profile class of this subscription.
+     * @return The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return
+     * {@code false}).
+     *
      * @hide
      */
     @SystemApi
-    public @SubscriptionManager.ProfileClass int getProfileClass() {
-        return this.mProfileClass;
+    @ProfileClass
+    public int getProfileClass() {
+        return mProfileClass;
     }
 
     /**
      * This method returns the type of a subscription. It can be
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
-     * @return the type of subscription
+     *
+     * @return The type of the subscription.
      */
-    public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
-        return mSubscriptionType;
+    @SubscriptionType
+    public int getSubscriptionType() {
+        return mType;
     }
 
     /**
@@ -679,7 +721,7 @@
      * returns true).
      *
      * @param context Context of the application to check.
-     * @return whether the app is authorized to manage this subscription per its metadata.
+     * @return Whether the app is authorized to manage this subscription per its metadata.
      * @hide
      * @deprecated - Do not use.
      */
@@ -700,7 +742,7 @@
      */
     @Deprecated
     public boolean canManageSubscription(Context context, String packageName) {
-        List<UiccAccessRule> allAccessRules = getAllAccessRules();
+        List<UiccAccessRule> allAccessRules = getAccessRules();
         if (allAccessRules == null) {
             return false;
         }
@@ -723,27 +765,17 @@
     }
 
     /**
-     * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who
-     * is authorized to manage this subscription.
-     * TODO and fix it properly in R / master: either deprecate this and have 3 APIs
-     *  native + carrier + all, or have this return all by default.
+     * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to
+     * manage this subscription.
+     *
      * @hide
      */
     @SystemApi
-    public @Nullable List<UiccAccessRule> getAccessRules() {
-        if (mNativeAccessRules == null) return null;
-        return Arrays.asList(mNativeAccessRules);
-    }
-
-    /**
-     * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs
-     * dictating who is authorized to manage this subscription.
-     * @hide
-     */
-    public @Nullable List<UiccAccessRule> getAllAccessRules() {
+    @Nullable
+    public List<UiccAccessRule> getAccessRules() {
         List<UiccAccessRule> merged = new ArrayList<>();
         if (mNativeAccessRules != null) {
-            merged.addAll(getAccessRules());
+            merged.addAll(Arrays.asList(mNativeAccessRules));
         }
         if (mCarrierConfigAccessRules != null) {
             merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
@@ -762,50 +794,38 @@
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile
      * owner access is deprecated and will be removed in a future release.
      *
-     * @return the card string of the SIM card which contains the subscription or an empty string
+     * @return The card string of the SIM card which contains the subscription or an empty string
      * if these requirements are not met. The card string is the ICCID for UICCs or the EID for
      * eUICCs.
+     *
      * @hide
-     * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java
      */
+    @NonNull
     public String getCardString() {
-        return this.mCardString;
+        return mCardString;
     }
 
     /**
-     * @hide
-     */
-    public void clearCardString() {
-        this.mCardString = "";
-    }
-
-    /**
-     * Returns the card ID of the SIM card which contains the subscription (see
-     * {@link UiccCardInfo#getCardId()}.
-     * @return the cardId
+     * @return The card ID of the SIM card which contains the subscription.
+     *
+     * @see UiccCardInfo#getCardId().
      */
     public int getCardId() {
-        return this.mCardId;
+        return mCardId;
     }
     /**
-     * Returns the port index of the SIM card which contains the subscription.
-     *
-     * @return the portIndex
+     * @return The port index of the SIM card which contains the subscription.
      */
     public int getPortIndex() {
-        return this.mPortIndex;
+        return mPortIndex;
     }
 
     /**
-     * Set whether the subscription's group is disabled.
-     * @hide
-     */
-    public void setGroupDisabled(boolean isGroupDisabled) {
-        this.mIsGroupDisabled = isGroupDisabled;
-    }
-
-    /**
-     * Return whether the subscription's group is disabled.
+     * @return {@code true} if the group of the subscription is disabled. This is only useful if
+     * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic)
+     * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we
+     * should disable this opportunistic subscription.
+     *
      * @hide
      */
     @SystemApi
@@ -814,7 +834,7 @@
     }
 
     /**
-     * Return whether uicc applications are set to be enabled or disabled.
+     * @return {@code true} if Uicc applications are set to be enabled or disabled.
      * @hide
      */
     @SystemApi
@@ -825,56 +845,50 @@
     /**
      * Get the usage setting for this subscription.
      *
-     * @return the usage setting used for this subscription.
+     * @return The usage setting used for this subscription.
      */
-    public @UsageSetting int getUsageSetting() {
+    @UsageSetting
+    public int getUsageSetting() {
         return mUsageSetting;
     }
 
-    public static final @android.annotation.NonNull
-            Parcelable.Creator<SubscriptionInfo> CREATOR =
-                    new Parcelable.Creator<SubscriptionInfo>() {
+    @NonNull
+    public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
+            new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
-            int id = source.readInt();
-            String iccId = source.readString();
-            int simSlotIndex = source.readInt();
-            CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            int nameSource = source.readInt();
-            int iconTint = source.readInt();
-            String number = source.readString();
-            int dataRoaming = source.readInt();
-            String mcc = source.readString();
-            String mnc = source.readString();
-            String countryIso = source.readString();
-            boolean isEmbedded = source.readBoolean();
-            UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
-            String cardString = source.readString();
-            int cardId = source.readInt();
-            int portId = source.readInt();
-            boolean isOpportunistic = source.readBoolean();
-            String groupUUID = source.readString();
-            boolean isGroupDisabled = source.readBoolean();
-            int carrierid = source.readInt();
-            int profileClass = source.readInt();
-            int subType = source.readInt();
-            String[] ehplmns = source.createStringArray();
-            String[] hplmns = source.createStringArray();
-            String groupOwner = source.readString();
-            UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
-                UiccAccessRule.CREATOR);
-            boolean areUiccApplicationsEnabled = source.readBoolean();
-            int usageSetting = source.readInt();
-
-            SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
-                    carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
-                    mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
-                    isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
-                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
-                    portId, usageSetting);
-            info.setAssociatedPlmns(ehplmns, hplmns);
-            return info;
+            return new Builder()
+                    .setId(source.readInt())
+                    .setIccId(source.readString())
+                    .setSimSlotIndex(source.readInt())
+                    .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setNameSource(source.readInt())
+                    .setIconTint(source.readInt())
+                    .setNumber(source.readString())
+                    .setDataRoaming(source.readInt())
+                    .setMcc(source.readString())
+                    .setMnc(source.readString())
+                    .setCountryIso(source.readString())
+                    .setEmbedded(source.readBoolean())
+                    .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR))
+                    .setCardString(source.readString())
+                    .setCardId(source.readInt())
+                    .setPortIndex(source.readInt())
+                    .setOpportunistic(source.readBoolean())
+                    .setGroupUuid(source.readString8())
+                    .setGroupDisabled(source.readBoolean())
+                    .setCarrierId(source.readInt())
+                    .setProfileClass(source.readInt())
+                    .setType(source.readInt())
+                    .setEhplmns(source.createStringArray())
+                    .setHplmns(source.createStringArray())
+                    .setGroupOwner(source.readString())
+                    .setCarrierConfigAccessRules(source.createTypedArray(
+                            UiccAccessRule.CREATOR))
+                    .setUiccApplicationsEnabled(source.readBoolean())
+                    .setUsageSetting(source.readInt())
+                    .build();
         }
 
         @Override
@@ -904,11 +918,11 @@
         dest.writeInt(mCardId);
         dest.writeInt(mPortIndex);
         dest.writeBoolean(mIsOpportunistic);
-        dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString());
+        dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString());
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
         dest.writeInt(mProfileClass);
-        dest.writeInt(mSubscriptionType);
+        dest.writeInt(mType);
         dest.writeStringArray(mEhplmns);
         dest.writeStringArray(mHplmns);
         dest.writeString(mGroupOwner);
@@ -923,6 +937,11 @@
     }
 
     /**
+     * Get ICCID stripped PII information on user build.
+     *
+     * @param iccId The original ICCID.
+     * @return The stripped string.
+     *
      * @hide
      */
     public static String givePrintableIccid(String iccId) {
@@ -951,12 +970,12 @@
                 + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " portIndex=" + mPortIndex
-                + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+                + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " profileClass=" + mProfileClass
                 + " ehplmns=" + Arrays.toString(mEhplmns)
                 + " hplmns=" + Arrays.toString(mHplmns)
-                + " subscriptionType=" + mSubscriptionType
+                + " mType=" + mType
                 + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
                 + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
@@ -966,7 +985,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
-                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+                mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
                 mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules),
                 mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner,
                 mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting);
@@ -974,16 +993,9 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (obj == null) return false;
-        if (obj == this) return true;
-
-        SubscriptionInfo toCompare;
-        try {
-            toCompare = (SubscriptionInfo) obj;
-        } catch (ClassCastException ex) {
-            return false;
-        }
-
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        SubscriptionInfo toCompare = (SubscriptionInfo) obj;
         return mId == toCompare.mId
                 && mSimSlotIndex == toCompare.mSimSlotIndex
                 && mNameSource == toCompare.mNameSource
@@ -994,7 +1006,7 @@
                 && mIsGroupDisabled == toCompare.mIsGroupDisabled
                 && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
                 && mCarrierId == toCompare.mCarrierId
-                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+                && Objects.equals(mGroupUuid, toCompare.mGroupUuid)
                 && Objects.equals(mIccId, toCompare.mIccId)
                 && Objects.equals(mNumber, toCompare.mNumber)
                 && Objects.equals(mMcc, toCompare.mMcc)
@@ -1012,4 +1024,629 @@
                 && Arrays.equals(mHplmns, toCompare.mHplmns)
                 && mUsageSetting == toCompare.mUsageSetting;
     }
+
+    /**
+     * The builder class of {@link SubscriptionInfo}.
+     *
+     * @hide
+     */
+    public static class Builder {
+        /**
+         * The subscription id.
+         */
+        private int mId = 0;
+
+        /**
+         * The ICCID of the SIM that is associated with this subscription, empty if unknown.
+         */
+        @NonNull
+        private String mIccId = "";
+
+        /**
+         * The index of the SIM slot that currently contains the subscription and not necessarily
+         * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the
+         * subscription is inactive.
+         */
+        private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mDisplayName = "";
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mCarrierName = "";
+
+        /**
+         * The source of the carrier name.
+         */
+        @SimDisplayNameSource
+        private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+
+        /**
+         * The color to be used for tinting the icon when displaying to the user.
+         */
+        private int mIconTint = 0;
+
+        /**
+         * The number presented to the user identify this subscription.
+         */
+        @NonNull
+        private String mNumber = "";
+
+        /**
+         * Whether user enables data roaming for this subscription or not. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+         */
+        private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE;
+
+        /**
+         * SIM icon bitmap cache.
+         */
+        @Nullable
+        private Bitmap mIconBitmap = null;
+
+        /**
+         * The mobile country code.
+         */
+        @Nullable
+        private String mMcc = null;
+
+        /**
+         * The mobile network code.
+         */
+        @Nullable
+        private String mMnc = null;
+
+        /**
+         * EHPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mEhplmns = new String[0];
+
+        /**
+         * HPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mHplmns = new String[0];
+
+        /**
+         * The ISO Country code for the subscription's provider.
+         */
+        @NonNull
+        private String mCountryIso = "";
+
+        /**
+         * Whether the subscription is from eSIM.
+         */
+        private boolean mIsEmbedded = false;
+
+        /**
+         * The native access rules for this subscription, if it is embedded and defines any. This
+         * does not include access rules for non-embedded subscriptions.
+         */
+        @Nullable
+        private UiccAccessRule[] mNativeAccessRules = null;
+
+        /**
+         * The card string of the SIM card.
+         */
+        @NonNull
+        private String mCardString = "";
+
+        /**
+         * The card ID of the SIM card which contains the subscription.
+         */
+        private int mCardId = -1;
+
+        /**
+         * Whether the subscription is opportunistic or not.
+         */
+        private boolean mIsOpportunistic = false;
+
+        /**
+         * The group UUID of the subscription group.
+         */
+        @Nullable
+        private ParcelUuid mGroupUuid = null;
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         */
+        private boolean mIsGroupDisabled = false;
+
+        /**
+         * The carrier id.
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+        /**
+         * The profile class populated from the profile metadata if present. Otherwise, the profile
+         * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile
+         * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+         * {@code false}).
+         */
+        @ProfileClass
+        private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET;
+
+        /**
+         * The subscription type.
+         */
+        @SubscriptionType
+        private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM;
+
+        /**
+         * The owner package of group the subscription belongs to.
+         */
+        @NonNull
+        private String mGroupOwner = "";
+
+        /**
+         * The carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         */
+        @Nullable
+        private UiccAccessRule[] mCarrierConfigAccessRules = null;
+
+        /**
+         * Whether Uicc applications are configured to enable or not.
+         */
+        private boolean mAreUiccApplicationsEnabled = true;
+
+        /**
+         * the port index of the Uicc card.
+         */
+        private int mPortIndex = 0;
+
+        /**
+         * Subscription's preferred usage setting.
+         */
+        @UsageSetting
+        private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+        /**
+         * Default constructor.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructor from {@link SubscriptionInfo}.
+         *
+         * @param info The subscription info.
+         */
+        public Builder(@NonNull SubscriptionInfo info) {
+            mId = info.mId;
+            mIccId = info.mIccId;
+            mSimSlotIndex = info.mSimSlotIndex;
+            mDisplayName = info.mDisplayName;
+            mCarrierName = info.mCarrierName;
+            mNameSource = info.mNameSource;
+            mIconTint = info.mIconTint;
+            mNumber = info.mNumber;
+            mDataRoaming = info.mDataRoaming;
+            mIconBitmap = info.mIconBitmap;
+            mMcc = info.mMcc;
+            mMnc = info.mMnc;
+            mEhplmns = info.mEhplmns;
+            mHplmns = info.mHplmns;
+            mCountryIso = info.mCountryIso;
+            mIsEmbedded = info.mIsEmbedded;
+            mNativeAccessRules = info.mNativeAccessRules;
+            mCardString = info.mCardString;
+            mCardId = info.mCardId;
+            mIsOpportunistic = info.mIsOpportunistic;
+            mGroupUuid = info.mGroupUuid;
+            mIsGroupDisabled = info.mIsGroupDisabled;
+            mCarrierId = info.mCarrierId;
+            mProfileClass = info.mProfileClass;
+            mType = info.mType;
+            mGroupOwner = info.mGroupOwner;
+            mCarrierConfigAccessRules = info.mCarrierConfigAccessRules;
+            mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled;
+            mPortIndex = info.mPortIndex;
+            mUsageSetting = info.mUsageSetting;
+        }
+
+        /**
+         * Set the subscription id.
+         *
+         * @param id The subscription id.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setId(int id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Set the ICCID of the SIM that is associated with this subscription.
+         *
+         * @param iccId The ICCID of the SIM that is associated with this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIccId(@Nullable String iccId) {
+            mIccId = TextUtils.emptyIfNull(iccId);
+            return this;
+        }
+
+        /**
+         * Set the SIM index of the slot that currently contains the subscription. Set to
+         * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive.
+         *
+         * @param simSlotIndex The SIM slot index.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setSimSlotIndex(int simSlotIndex) {
+            mSimSlotIndex = simSlotIndex;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         *
+         * @param displayName The display name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDisplayName(@Nullable CharSequence displayName) {
+            mDisplayName = displayName == null ? "" : displayName;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         *
+         * @param carrierName The carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierName(@Nullable CharSequence carrierName) {
+            mCarrierName = carrierName == null ? "" : carrierName;
+            return this;
+        }
+
+        /**
+         * Set the source of the carrier name.
+         *
+         * @param nameSource The source of the carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNameSource(@SimDisplayNameSource int nameSource) {
+            mNameSource = nameSource;
+            return this;
+        }
+
+        /**
+         * Set the color to be used for tinting the icon when displaying to the user.
+         *
+         * @param iconTint The color to be used for tinting the icon when displaying to the user.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIconTint(int iconTint) {
+            mIconTint = iconTint;
+            return this;
+        }
+
+        /**
+         * Set the number presented to the user identify this subscription.
+         *
+         * @param number the number presented to the user identify this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNumber(@Nullable String number) {
+            mNumber = TextUtils.emptyIfNull(number);
+            return this;
+        }
+
+        /**
+         * Set whether user enables data roaming for this subscription or not.
+         *
+         * @param dataRoaming Data roaming mode. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDataRoaming(int dataRoaming) {
+            mDataRoaming = dataRoaming;
+            return this;
+        }
+
+        /**
+         * Set SIM icon bitmap cache.
+         *
+         * @param iconBitmap SIM icon bitmap cache.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIcon(@Nullable Bitmap iconBitmap) {
+            mIconBitmap = iconBitmap;
+            return this;
+        }
+
+        /**
+         * Set the mobile country code.
+         *
+         * @param mcc The mobile country code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMcc(@Nullable String mcc) {
+            mMcc = mcc;
+            return this;
+        }
+
+        /**
+         * Set the mobile network code.
+         *
+         * @param mnc Mobile network code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMnc(@Nullable String mnc) {
+            mMnc = mnc;
+            return this;
+        }
+
+        /**
+         * Set EHPLMNs associated with the subscription.
+         *
+         * @param ehplmns EHPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEhplmns(@Nullable String[] ehplmns) {
+            mEhplmns = ehplmns == null ? new String[0] : ehplmns;
+            return this;
+        }
+
+        /**
+         * Set HPLMNs associated with the subscription.
+         *
+         * @param hplmns HPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setHplmns(@Nullable String[] hplmns) {
+            mHplmns = hplmns == null ? new String[0] : hplmns;
+            return this;
+        }
+
+        /**
+         * Set the ISO Country code for the subscription's provider.
+         *
+         * @param countryIso The ISO Country code for the subscription's provider.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCountryIso(@Nullable String countryIso) {
+            mCountryIso = TextUtils.emptyIfNull(countryIso);
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is from eSIM or not.
+         *
+         * @param isEmbedded {@code true} if the subscription is from eSIM.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEmbedded(boolean isEmbedded) {
+            mIsEmbedded = isEmbedded;
+            return this;
+        }
+
+        /**
+         * Set the native access rules for this subscription, if it is embedded and defines any.
+         * This does not include access rules for non-embedded subscriptions.
+         *
+         * @param nativeAccessRules The native access rules for this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) {
+            mNativeAccessRules = nativeAccessRules;
+            return this;
+        }
+
+        /**
+         * Set the card string of the SIM card.
+         *
+         * @param cardString The card string of the SIM card.
+         * @return The builder.
+         *
+         * @see #getCardString()
+         */
+        @NonNull
+        public Builder setCardString(@Nullable String cardString) {
+            mCardString = TextUtils.emptyIfNull(cardString);
+            return this;
+        }
+
+        /**
+         * Set the card ID of the SIM card which contains the subscription.
+         *
+         * @param cardId The card ID of the SIM card which contains the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCardId(int cardId) {
+            mCardId = cardId;
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is opportunistic or not.
+         *
+         * @param isOpportunistic {@code true} if the subscription is opportunistic.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setOpportunistic(boolean isOpportunistic) {
+            mIsOpportunistic = isOpportunistic;
+            return this;
+        }
+
+        /**
+         * Set the group UUID of the subscription group.
+         *
+         * @param groupUuid The group UUID.
+         * @return The builder.
+         *
+         * @see #getGroupUuid()
+         */
+        @NonNull
+        public Builder setGroupUuid(@Nullable String groupUuid) {
+            mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
+            return this;
+        }
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         *
+         * @param isGroupDisabled {@code true} if group of the subscription is disabled.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupDisabled(boolean isGroupDisabled) {
+            mIsGroupDisabled = isGroupDisabled;
+            return this;
+        }
+
+        /**
+         * Set the subscription carrier id.
+         *
+         * @param carrierId The carrier id.
+         * @return The builder
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        @NonNull
+        public Builder setCarrierId(int carrierId) {
+            mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
+         * Set the profile class populated from the profile metadata if present.
+         *
+         * @param profileClass the profile class populated from the profile metadata if present.
+         * @return The builder
+         *
+         * @see #getProfileClass()
+         */
+        @NonNull
+        public Builder setProfileClass(@ProfileClass int profileClass) {
+            mProfileClass = profileClass;
+            return this;
+        }
+
+        /**
+         * Set the subscription type.
+         *
+         * @param type Subscription type.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setType(@SubscriptionType int type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Set the owner package of group the subscription belongs to.
+         *
+         * @param groupOwner Owner package of group the subscription belongs to.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupOwner(@Nullable String groupOwner) {
+            mGroupOwner = TextUtils.emptyIfNull(groupOwner);
+            return this;
+        }
+
+        /**
+         * Set the carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         *
+         * @param carrierConfigAccessRules The carrier certificates for this subscription
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierConfigAccessRules(
+                @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+            mCarrierConfigAccessRules = carrierConfigAccessRules;
+            return this;
+        }
+
+        /**
+         * Set whether Uicc applications are configured to enable or not.
+         *
+         * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to
+         * enable.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) {
+            mAreUiccApplicationsEnabled = uiccApplicationsEnabled;
+            return this;
+        }
+
+        /**
+         * Set the port index of the Uicc card.
+         *
+         * @param portIndex The port index of the Uicc card.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setPortIndex(int portIndex) {
+            mPortIndex = portIndex;
+            return this;
+        }
+
+        /**
+         * Set subscription's preferred usage setting.
+         *
+         * @param usageSetting Subscription's preferred usage setting.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUsageSetting(@UsageSetting int usageSetting) {
+            mUsageSetting = usageSetting;
+            return this;
+        }
+
+        /**
+         * Build the {@link SubscriptionInfo}.
+         *
+         * @return The {@link SubscriptionInfo} instance.
+         */
+        public SubscriptionInfo build() {
+            return new SubscriptionInfo(this);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c608507..193c2c1 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -509,18 +509,14 @@
     public static final String TP_MESSAGE_REF = SimInfo.COLUMN_TP_MESSAGE_REF;
 
     /**
-     * TelephonyProvider column name data_enabled_override_rules.
-     * It's a list of rules for overriding data enabled settings. The syntax is
-     * For example, "mms=nonDefault" indicates enabling data for mms in non-default subscription.
-     * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default
-     * subscription and while is in voice call.
+     * TelephonyProvider column name enabled_mobile_data_policies.
+     * A list of mobile data policies, each of which represented by an integer and joint by ",".
      *
      * Default value is empty string.
-     *
      * @hide
      */
-    public static final String DATA_ENABLED_OVERRIDE_RULES =
-            SimInfo.COLUMN_DATA_ENABLED_OVERRIDE_RULES;
+    public static final String ENABLED_MOBILE_DATA_POLICIES =
+            SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -1910,30 +1906,6 @@
     }
 
     /**
-     * @return the count of all subscriptions in the database, this includes
-     * all subscriptions that have been seen.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int getAllSubscriptionInfoCount() {
-        if (VDBG) logd("[getAllSubscriptionInfoCount]+");
-
-        int result = 0;
-
-        try {
-            ISub iSub = TelephonyManager.getSubscriptionService();
-            if (iSub != null) {
-                result = iSub.getAllSubInfoCount(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        }
-
-        return result;
-    }
-
-    /**
      *
      * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
@@ -2332,24 +2304,6 @@
     }
 
     /**
-     * Return the SubscriptionInfo for default voice subscription.
-     *
-     * Will return null on data only devices, or on error.
-     *
-     * @return the SubscriptionInfo for the default SMS subscription.
-     * @hide
-     */
-    public SubscriptionInfo getDefaultSmsSubscriptionInfo() {
-        return getActiveSubscriptionInfo(getDefaultSmsSubscriptionId());
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public int getDefaultSmsPhoneId() {
-        return getPhoneId(getDefaultSmsSubscriptionId());
-    }
-
-    /**
      * Returns the system's default data subscription id.
      *
      * On a voice only device or on error, will return INVALID_SUBSCRIPTION_ID.
@@ -2397,12 +2351,6 @@
     }
 
     /** @hide */
-    @UnsupportedAppUsage
-    public int getDefaultDataPhoneId() {
-        return getPhoneId(getDefaultDataSubscriptionId());
-    }
-
-    /** @hide */
     public void clearSubscriptionInfo() {
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
@@ -2416,21 +2364,6 @@
         return;
     }
 
-    //FIXME this is vulnerable to race conditions
-    /** @hide */
-    public boolean allDefaultsSelected() {
-        if (!isValidSubscriptionId(getDefaultDataSubscriptionId())) {
-            return false;
-        }
-        if (!isValidSubscriptionId(getDefaultSmsSubscriptionId())) {
-            return false;
-        }
-        if (!isValidSubscriptionId(getDefaultVoiceSubscriptionId())) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Check if the supplied subscription ID is valid.
      *
@@ -3106,7 +3039,7 @@
     @SystemApi
     public boolean canManageSubscription(@NonNull SubscriptionInfo info,
             @NonNull String packageName) {
-        if (info == null || info.getAllAccessRules() == null || packageName == null) {
+        if (info == null || info.getAccessRules() == null || packageName == null) {
             return false;
         }
         PackageManager packageManager = mContext.getPackageManager();
@@ -3118,7 +3051,7 @@
             logd("Unknown package: " + packageName);
             return false;
         }
-        for (UiccAccessRule rule : info.getAllAccessRules()) {
+        for (UiccAccessRule rule : info.getAccessRules()) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 return true;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 12b4114..8818ac2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -191,9 +191,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
 
-    // Null IMEI anomaly uuid
-    private static final UUID IMEI_ANOMALY_UUID = UUID.fromString(
-            "83905f14-6455-450c-be29-8206f0427fe9");
     /**
      * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
      * into the ResultReceiver Bundle.
@@ -2184,11 +2181,7 @@
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei() {
-        String imei = getImei(getSlotIndex());
-        if (imei == null) {
-            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null.");
-        }
-        return imei;
+        return getImei(getSlotIndex());
     }
 
     /**
@@ -2231,10 +2224,7 @@
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
-        if (telephony == null) {
-            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null");
-            return null;
-        }
+        if (telephony == null) return null;
 
         try {
             return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag());
@@ -17122,6 +17112,266 @@
     }
 
     /**
+     * A premium capability boosting the network to allow real-time interactive traffic.
+     * Corresponds to NetworkCapabilities#NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC.
+     */
+    // TODO(b/245748544): add @link once NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC is defined.
+    public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1;
+
+    /**
+     * Purchasable premium capabilities.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PREMIUM_CAPABILITY_" }, value = {
+            PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC})
+    public @interface PremiumCapability {}
+
+    /**
+     * Returns the premium capability {@link PremiumCapability} as a String.
+     *
+     * @param capability The premium capability.
+     * @return The premium capability as a String.
+     * @hide
+     */
+    public static String convertPremiumCapabilityToString(@PremiumCapability int capability) {
+        switch (capability) {
+            case PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC:
+                return "REALTIME_INTERACTIVE_TRAFFIC";
+            default:
+                return "UNKNOWN (" + capability + ")";
+        }
+    }
+
+    /**
+     * Check whether the given premium capability is available for purchase from the carrier.
+     * If this is {@code true}, the capability can be purchased from the carrier using
+     * {@link #purchasePremiumCapability(int, Executor, Consumer)}.
+     *
+     * @param capability The premium capability to check.
+     * @return Whether the given premium capability is available to purchase.
+     * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
+    public boolean isPremiumCapabilityAvailableForPurchase(@PremiumCapability int capability) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                throw new IllegalStateException("telephony service is null.");
+            }
+            return telephony.isPremiumCapabilityAvailableForPurchase(capability, getSubId());
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return false;
+    }
+
+    /**
+     * Purchase premium capability request was successful. Subsequent attempts will return
+     * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
+     * The expiry time is determined by the type or duration of boost purchased from the carrier,
+     * provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1;
+
+    /**
+     * Purchase premium capability failed because the request is throttled for the amount of time
+     * specified by {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+     * or {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
+     * Subsequent attempts will return the same error until the request is no longer throttled
+     * or throttling conditions change.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2;
+
+    /**
+     * Purchase premium capability failed because it is already purchased and available.
+     * Subsequent attempts will return the same error until the booster expires.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3;
+
+    /**
+     * Purchase premium capability failed because a request was already made and is in progress.
+     * This may have been requested by either the same app or another app.
+     * Subsequent attempts will return the same error until the previous request completes.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4;
+
+    /**
+     * Purchase premium capability failed because the user disabled the feature.
+     * Subsequent attempts will return the same error until the user re-enables the feature.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 5;
+
+    /**
+     * Purchase premium capability failed because the user canceled the operation.
+     * 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_CANCELED = 6;
+
+    /**
+     * Purchase premium capability failed because the carrier disabled or does not support
+     * the capability, as specified in
+     * {@link CarrierConfigManager#KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY}.
+     * Subsequent attempts will return the same error until the carrier enables the feature.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7;
+
+    /**
+     * Purchase premium capability failed because the carrier app did not indicate success.
+     * Subsequent attempts will be throttled for the amount of time specified by
+     * {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+     * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8;
+
+    /**
+     * Purchase premium capability failed because we did not receive a response from the user
+     * for the booster notification within the time specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
+     * The booster notification will be automatically dismissed and 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_TIMEOUT = 9;
+
+    /**
+     * Purchase premium capability failed because the device does not support the feature.
+     * Subsequent attempts will return the same error.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10;
+
+    /**
+     * Purchase premium capability failed because the telephony service is down or unavailable.
+     * Subsequent attempts will return the same error until request conditions are satisfied.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11;
+
+    /**
+     * Purchase premium capability failed because the network is not available.
+     * Subsequent attempts will return the same error until network conditions change.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12;
+
+    /**
+     * Purchase premium capability failed because the network is congested.
+     * Subsequent attempts will be throttled for the amount of time specified by
+     * {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+     * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+     * Throttling will be reevaluated when the network is no longer congested.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13;
+
+    /**
+     * Results of the purchase premium capability request.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PURCHASE_PREMIUM_CAPABILITY_RESULT_" }, value = {
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED})
+    public @interface PurchasePremiumCapabilityResult {}
+
+    /**
+     * Returns the purchase result {@link PurchasePremiumCapabilityResult} as a String.
+     *
+     * @param result The purchase premium capability result.
+     * @return The purchase result as a String.
+     * @hide
+     */
+    public static String convertPurchaseResultToString(
+            @PurchasePremiumCapabilityResult int result) {
+        switch (result) {
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS:
+                return "SUCCESS";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED:
+                return "THROTTLED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED:
+                return "ALREADY_PURCHASED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS:
+                return "ALREADY_IN_PROGRESS";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED:
+                return "USER_DISABLED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED:
+                return "USER_CANCELED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED:
+                return "CARRIER_DISABLED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR:
+                return "CARRIER_ERROR";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT:
+                return "TIMEOUT";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED:
+                return "FEATURE_NOT_SUPPORTED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED:
+                return "REQUEST_FAILED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE:
+                return "NETWORK_NOT_AVAILABLE";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
+                return "NETWORK_CONGESTED";
+            default:
+                return "UNKNOWN (" + result + ")";
+        }
+    }
+
+    /**
+     * Purchase the given premium capability from the carrier.
+     * This requires user action to purchase the boost from the carrier.
+     * If this returns {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS} or
+     * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}, applications can request
+     * the premium capability via {@link ConnectivityManager#requestNetwork}.
+     *
+     * @param capability The premium capability to purchase.
+     * @param executor The callback executor for the response.
+     * @param callback The result of the purchase request.
+     *                 One of {@link PurchasePremiumCapabilityResult}.
+     * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
+     * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid
+     */
+    @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
+    public void purchasePremiumCapability(@PremiumCapability int capability,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @PurchasePremiumCapabilityResult Consumer<Integer> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+            @Override
+            public void accept(int result) {
+                executor.execute(() -> callback.accept(result));
+            }
+        };
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                callback.accept(PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED);
+                return;
+            }
+            telephony.purchasePremiumCapability(capability, internalCallback, getSubId());
+        } catch (RemoteException ex) {
+            callback.accept(PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED);
+        }
+    }
+
+    /**
      * Get last known cell identity.
      * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
      * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 6df9f9b..fa3f15d 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -43,6 +43,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
@@ -1442,7 +1443,7 @@
      */
     @SystemApi
     public static @ApnType int getApnTypeInt(@NonNull @ApnTypeString String apnType) {
-        return APN_TYPE_STRING_MAP.getOrDefault(apnType.toLowerCase(), 0);
+        return APN_TYPE_STRING_MAP.getOrDefault(apnType.toLowerCase(Locale.ROOT), 0);
     }
 
     /**
@@ -1457,7 +1458,7 @@
         } else {
             int result = 0;
             for (String str : types.split(",")) {
-                Integer type = APN_TYPE_STRING_MAP.get(str.toLowerCase());
+                Integer type = APN_TYPE_STRING_MAP.get(str.toLowerCase(Locale.ROOT));
                 if (type != null) {
                     result |= type;
                 }
@@ -1468,7 +1469,8 @@
 
     /** @hide */
     public static int getMvnoTypeIntFromString(String mvnoType) {
-        String mvnoTypeString = TextUtils.isEmpty(mvnoType) ? mvnoType : mvnoType.toLowerCase();
+        String mvnoTypeString = TextUtils.isEmpty(mvnoType)
+                ? mvnoType : mvnoType.toLowerCase(Locale.ROOT);
         Integer mvnoTypeInt = MVNO_TYPE_STRING_MAP.get(mvnoTypeString);
         return  mvnoTypeInt == null ? UNSPECIFIED_INT : mvnoTypeInt;
     }
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 700d615..d8b2cbe 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -725,7 +725,7 @@
 
     @Override
     public void onDestroy() {
-        mHandlerThread.quit();
+        mHandlerThread.quitSafely();
         super.onDestroy();
     }
 
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 7d63688..a2d2019 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -48,6 +48,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 
 /**
@@ -1524,7 +1525,7 @@
             return false;
         }
         try {
-            return getIEuiccController().isSupportedCountry(countryIso.toUpperCase());
+            return getIEuiccController().isSupportedCountry(countryIso.toUpperCase(Locale.ROOT));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/telephony/java/android/telephony/ims/RcsConfig.java b/telephony/java/android/telephony/ims/RcsConfig.java
index fd8d8a7..32d686d 100644
--- a/telephony/java/android/telephony/ims/RcsConfig.java
+++ b/telephony/java/android/telephony/ims/RcsConfig.java
@@ -36,6 +36,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -188,16 +189,17 @@
             String tag = null;
             while (eventType != XmlPullParser.END_DOCUMENT && current != null) {
                 if (eventType == XmlPullParser.START_TAG) {
-                    tag = xpp.getName().trim().toLowerCase();
+                    tag = xpp.getName().trim().toLowerCase(Locale.ROOT);
                     if (TAG_CHARACTERISTIC.equals(tag)) {
                         int count = xpp.getAttributeCount();
                         String type = null;
                         if (count > 0) {
                             for (int i = 0; i < count; i++) {
-                                String name = xpp.getAttributeName(i).trim().toLowerCase();
+                                String name = xpp.getAttributeName(i).trim()
+                                        .toLowerCase(Locale.ROOT);
                                 if (ATTRIBUTE_TYPE.equals(name)) {
                                     type = xpp.getAttributeValue(xpp.getAttributeNamespace(i),
-                                            name).trim().toLowerCase();
+                                            name).trim().toLowerCase(Locale.ROOT);
                                     break;
                                 }
                             }
@@ -211,10 +213,11 @@
                         String value = null;
                         if (count > 1) {
                             for (int i = 0; i < count; i++) {
-                                String name = xpp.getAttributeName(i).trim().toLowerCase();
+                                String name = xpp.getAttributeName(i).trim()
+                                        .toLowerCase(Locale.ROOT);
                                 if (ATTRIBUTE_NAME.equals(name)) {
                                     key = xpp.getAttributeValue(xpp.getAttributeNamespace(i),
-                                            name).trim().toLowerCase();
+                                            name).trim().toLowerCase(Locale.ROOT);
                                 } else if (ATTRIBUTE_VALUE.equals(name)) {
                                     value = xpp.getAttributeValue(xpp.getAttributeNamespace(i),
                                             name).trim();
@@ -226,7 +229,7 @@
                         }
                     }
                 } else if (eventType == XmlPullParser.END_TAG) {
-                    tag = xpp.getName().trim().toLowerCase();
+                    tag = xpp.getName().trim().toLowerCase(Locale.ROOT);
                     if (TAG_CHARACTERISTIC.equals(tag)) {
                         current = current.getParent();
                     }
@@ -254,7 +257,7 @@
      * @return Returns the config value if it exists, or defaultVal.
      */
     public @Nullable String getString(@NonNull String tag, @Nullable String defaultVal) {
-        String value = mCurrent.getParmValue(tag.trim().toLowerCase());
+        String value = mCurrent.getParmValue(tag.trim().toLowerCase(Locale.ROOT));
         return value != null ?  value : defaultVal;
     }
 
@@ -296,21 +299,21 @@
      * @return Returns true if it exists, or false.
      */
     public boolean hasConfig(@NonNull String tag) {
-        return mCurrent.hasParm(tag.trim().toLowerCase());
+        return mCurrent.hasParm(tag.trim().toLowerCase(Locale.ROOT));
     }
 
     /**
      * Return the Characteristic with the given type
      */
     public @Nullable Characteristic getCharacteristic(@NonNull String type) {
-        return mCurrent.getSubByType(type.trim().toLowerCase());
+        return mCurrent.getSubByType(type.trim().toLowerCase(Locale.ROOT));
     }
 
     /**
      * Check whether the Characteristic with the given type exists
      */
     public boolean hasCharacteristic(@NonNull String type) {
-        return mCurrent.getSubByType(type.trim().toLowerCase()) != null;
+        return mCurrent.getSubByType(type.trim().toLowerCase(Locale.ROOT)) != null;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 1e38b92..917f35b 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -30,14 +30,6 @@
     List<SubscriptionInfo> getAllSubInfoList(String callingPackage, String callingFeatureId);
 
     /**
-     * @param callingPackage The package maing the call.
-     * @param callingFeatureId The feature in the package
-     * @return the count of all subscriptions in the database, this includes
-     * all subscriptions that have been seen.
-     */
-    int getAllSubInfoCount(String callingPackage, String callingFeatureId);
-
-    /**
      * Get the active SubscriptionInfo with the subId key
      * @param subId The unique SubscriptionInfo key in database
      * @param callingPackage The package maing the call.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 850d268..648866b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2529,6 +2529,16 @@
     void getSlicingConfig(in ResultReceiver callback);
 
     /**
+     * Check whether the given premium capability is available for purchase from the carrier.
+     */
+    boolean isPremiumCapabilityAvailableForPurchase(int capability, int subId);
+
+    /**
+     * Purchase the given premium capability from the carrier.
+     */
+    void purchasePremiumCapability(int capability, IIntegerConsumer callback, int subId);
+
+    /**
      * Register an IMS connection state callback
      */
     void registerImsStateCallback(int subId, int feature, in IImsStateCallback cb,
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 39ab7eb..9892671 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -532,6 +532,10 @@
     int RIL_REQUEST_IS_VONR_ENABLED = 226;
     int RIL_REQUEST_SET_USAGE_SETTING = 227;
     int RIL_REQUEST_GET_USAGE_SETTING = 228;
+    int RIL_REQUEST_SET_EMERGENCY_MODE = 229;
+    int RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN = 230;
+    int RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN = 231;
+    int RIL_REQUEST_EXIT_EMERGENCY_MODE = 232;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -602,4 +606,5 @@
     int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
     int RIL_UNSOL_REGISTRATION_FAILED = 1104;
     int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
+    int RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT = 1106;
 }
diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp
index ddbf168..7fbe3b3 100644
--- a/tests/Codegen/Android.bp
+++ b/tests/Codegen/Android.bp
@@ -24,6 +24,14 @@
     plugins: [
         "staledataclass-annotation-processor",
     ],
+    // Exports needed for staledataclass-annotation-processor, see b/139342589.
+    javacflags: [
+        "-J--add-modules=jdk.compiler",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
     static_libs: [
         "junit",
         "hamcrest",
diff --git a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java
index 2e51570..761efe4 100644
--- a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java
+++ b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java
@@ -110,7 +110,7 @@
 
     private void test() {
         Intent intent = new Intent(this, FixVibrateSetting.class);
-        PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0);
+        PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 
         Notification n = new Notification.Builder(this)
                 .setSmallIcon(R.drawable.stat_sys_warning)
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index e173eba0..487a0c3 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -40,6 +40,8 @@
     <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">
         <uses-library android:name="android.test.runner"/>
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 566c725..d91aa1e 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -13,6 +13,8 @@
         <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" />
         <!-- restart launcher to activate TAPL -->
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
     </target_preparer>
diff --git a/tests/FlickerTests/res/anim/show_2000ms.xml b/tests/FlickerTests/res/anim/show_2000ms.xml
new file mode 100644
index 0000000..76e375f
--- /dev/null
+++ b/tests/FlickerTests/res/anim/show_2000ms.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.
+  -->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="2000"
+    android:fromXDelta="0"
+    android:toXDelta="0" />
\ No newline at end of file
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 1e798f3..8a1e1fa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,58 +21,53 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
 
 /**
- * Base test class containing common assertions for [ComponentMatcher.NAV_BAR],
- * [ComponentMatcher.TASK_BAR], [ComponentMatcher.STATUS_BAR], and general assertions
+ * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
+ * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions
  * (layers visible in consecutive states, entire screen covered, etc.)
  */
-abstract class BaseTest @JvmOverloads constructor(
+abstract class BaseTest
+@JvmOverloads
+constructor(
     protected val testSpec: FlickerTestParameter,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(
-                instrumentation,
-                clearCacheAfterParsing = false
-            ).currentState.wmState.isTablet
+            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
+                .currentState
+                .wmState
+                .isTablet
         )
         tapl.setExpectedRotationCheckEnabled(true)
     }
 
-    /**
-     * Specification of the test transition to execute
-     */
+    /** Specification of the test transition to execute */
     abstract val transition: FlickerBuilder.() -> Unit
 
     /**
-     * Entry point for the test runner. It will use this method to initialize and cache
-     * flicker executions
+     * Entry point for the test runner. It will use this method to initialize and cache flicker
+     * executions
      */
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            setup {
-                testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet)
-            }
+            setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
             transition()
         }
     }
 
-    /**
-     * Checks that all parts of the screen are covered during the transition
-     */
-    @Presubmit
-    @Test
-    open fun entireScreenCovered() = testSpec.entireScreenCovered()
+    /** Checks that all parts of the screen are covered during the transition */
+    @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] layer is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
      *
      * Note: Phones only
      */
@@ -84,7 +79,8 @@
     }
 
     /**
-     * Checks the position of the [ComponentMatcher.NAV_BAR] at the start and end of the transition
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
      *
      * Note: Phones only
      */
@@ -96,7 +92,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
      *
      * Note: Phones only
      */
@@ -108,7 +104,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] window is visible at the start and end of the
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the
      * transition
      *
      * Note: Large screen only
@@ -121,7 +117,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
      *
      * Note: Large screen only
      */
@@ -133,8 +129,8 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the start and end
-     * of the transition
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of
+     * the transition
      */
     @Presubmit
     @Test
@@ -142,7 +138,7 @@
         testSpec.statusBarLayerIsVisibleAtStartAndEnd()
 
     /**
-     * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
     @Presubmit
@@ -150,33 +146,30 @@
     open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
      */
     @Presubmit
     @Test
     open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that all layers that are visible on the trace, are visible for at least 2
-     * consecutive entries.
+     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
      */
     @Presubmit
     @Test
     open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers {
-            this.visibleLayersShownMoreThanOneConsecutiveEntry()
-        }
+        testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
     }
 
     /**
-     * Checks that all windows that are visible on the trace, are visible for at least 2
-     * consecutive entries.
+     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
      */
     @Presubmit
     @Test
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm {
-            this.visibleWindowsShownMoreThanOneConsecutiveEntry()
-        }
+        testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
 }
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 c6a7c88..bbffd08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -15,6 +15,7 @@
  */
 
 @file:JvmName("CommonAssertions")
+
 package com.android.server.wm.flicker
 
 import com.android.server.wm.flicker.helpers.WindowUtils
@@ -23,28 +24,24 @@
 import com.android.server.wm.traces.common.IComponentNameMatcher
 
 /**
- * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in
- * all WM trace entries
+ * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
+ * WM trace entries
  */
 fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
-    assertWm {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR)
-    }
+    assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in
- * all WM trace entries
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
+ * trace entries
  */
 fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
-    assertWm {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
-    }
+    assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the start
- * and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start and end of the WM trace
  */
 fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
     this.navBarWindowIsVisibleAtStart()
@@ -52,51 +49,43 @@
 }
 
 /**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the start
- * of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start of the WM trace
  */
 fun FlickerTestParameter.navBarWindowIsVisibleAtStart() {
-    assertWmStart {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
-    }
+    assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
- * end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
+ * of the WM trace
  */
 fun FlickerTestParameter.navBarWindowIsVisibleAtEnd() {
-    assertWmEnd {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
-    }
+    assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in
- * all WM trace entries
+ * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
+ * trace entries
  */
 fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
-    assertWm {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
-    }
+    assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in
- * all WM trace entries
+ * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
+ * trace entries
  */
 fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
-    assertWmEnd {
-        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
-    }
+    assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /**
- * If [allStates] is true, checks if the stack space of all displays is fully covered
- * by any visible layer, during the whole transitions
+ * If [allStates] is true, checks if the stack space of all displays is fully covered by any visible
+ * layer, during the whole transitions
  *
- * Otherwise, checks if the stack space of all displays is fully covered
- * by any visible layer, at the start and end of the transition
+ * Otherwise, checks if the stack space of all displays is fully covered by any visible layer, at
+ * the start and end of the transition
  *
  * @param allStates if all states should be checked, othersie, just initial and final
  */
@@ -124,27 +113,18 @@
     }
 }
 
-/**
- * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace
- */
+/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
 fun FlickerTestParameter.navBarLayerIsVisibleAtStart() {
-    assertLayersStart {
-        this.isVisible(ComponentNameMatcher.NAV_BAR)
-    }
+    assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
-/**
- * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace
- */
+/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
 fun FlickerTestParameter.navBarLayerIsVisibleAtEnd() {
-    assertLayersEnd {
-        this.isVisible(ComponentNameMatcher.NAV_BAR)
-    }
+    assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
- * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF
- * trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
  */
 fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
     this.navBarLayerIsVisibleAtStart()
@@ -152,32 +132,21 @@
 }
 
 /**
- * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF
- * trace
+ * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
  */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtStartAndEnd() {
     this.taskBarLayerIsVisibleAtStart()
     this.taskBarLayerIsVisibleAtEnd()
 }
 
-/**
- * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF
- * trace
- */
+/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
-    assertLayersStart {
-        this.isVisible(ComponentNameMatcher.TASK_BAR)
-    }
+    assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
-/**
- * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF
- * trace
- */
+/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
-    assertLayersEnd {
-        this.isVisible(ComponentNameMatcher.TASK_BAR)
-    }
+    assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /**
@@ -185,43 +154,40 @@
  * trace
  */
 fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
-    assertLayersStart {
-        this.isVisible(ComponentNameMatcher.STATUS_BAR)
-    }
-    assertLayersEnd {
-        this.isVisible(ComponentNameMatcher.STATUS_BAR)
-    }
+    assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
 }
 
 /**
- * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start
- * of the SF trace
+ * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
+ * the SF trace
  */
 fun FlickerTestParameter.navBarLayerPositionAtStart() {
     assertLayersStart {
-        val display = this.entry.displays.firstOrNull { !it.isVirtual }
-                ?: error("There is no display!")
+        val display =
+            this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
         this.visibleRegion(ComponentNameMatcher.NAV_BAR)
             .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
     }
 }
 
 /**
- * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end
- * of the SF trace
+ * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
+ * the SF trace
  */
 fun FlickerTestParameter.navBarLayerPositionAtEnd() {
     assertLayersEnd {
-        val display = this.entry.displays.minByOrNull { it.id }
-            ?: throw RuntimeException("There is no display!")
+        val display =
+            this.entry.displays.minByOrNull { it.id }
+                ?: throw RuntimeException("There is no display!")
         this.visibleRegion(ComponentNameMatcher.NAV_BAR)
             .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
     }
 }
 
 /**
- * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start
- * and end of the SF trace
+ * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
+ * end of the SF trace
  */
 fun FlickerTestParameter.navBarLayerPositionAtStartAndEnd() {
     navBarLayerPositionAtStart()
@@ -234,21 +200,23 @@
  */
 fun FlickerTestParameter.statusBarLayerPositionAtStart() {
     assertLayersStart {
-        val display = this.entry.displays.minByOrNull { it.id }
-            ?: throw RuntimeException("There is no display!")
+        val display =
+            this.entry.displays.minByOrNull { it.id }
+                ?: throw RuntimeException("There is no display!")
         this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
             .coversExactly(WindowUtils.getStatusBarPosition(display))
     }
 }
 
 /**
- * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end
- * of the SF trace
+ * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
+ * the SF trace
  */
 fun FlickerTestParameter.statusBarLayerPositionAtEnd() {
     assertLayersEnd {
-        val display = this.entry.displays.minByOrNull { it.id }
-            ?: throw RuntimeException("There is no display!")
+        val display =
+            this.entry.displays.minByOrNull { it.id }
+                ?: throw RuntimeException("There is no display!")
         this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
             .coversExactly(WindowUtils.getStatusBarPosition(display))
     }
@@ -264,23 +232,25 @@
 }
 
 /**
- * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover
- * the visibleRegion of the given app component exactly
+ * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
+ * visibleRegion of the given app component exactly
  */
 fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
     component: IComponentNameMatcher
 ) {
     assertLayers {
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
-            val snapshotLayers = it.subjects.filter { subject ->
-                subject.name.contains(
-                    ComponentNameMatcher.SNAPSHOT.toLayerName()) && subject.isVisible
-            }
+            val snapshotLayers =
+                it.subjects.filter { subject ->
+                    subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) &&
+                        subject.isVisible
+                }
             // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
             if (snapshotLayers.isNotEmpty()) {
-                val visibleAreas = snapshotLayers.mapNotNull { snapshotLayer ->
-                    snapshotLayer.layer?.visibleRegion
-                }.toTypedArray()
+                val visibleAreas =
+                    snapshotLayers
+                        .mapNotNull { snapshotLayer -> snapshotLayer.layer?.visibleRegion }
+                        .toTypedArray()
                 val snapshotRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
                 val appVisibleRegion = it.visibleRegion(component)
                 if (snapshotRegion.region.isNotEmpty) {
@@ -293,22 +263,33 @@
 
 /**
  * Asserts that:
+ * ```
  *     [originalLayer] is visible at the start of the trace
  *     [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer]
  *         becomes visible
  *     [newLayer] remains visible until the end of the trace
  *
- * @param originalLayer Layer that should be visible at the start
+ * @param originalLayer
+ * ```
+ * Layer that should be visible at the start
  * @param newLayer Layer that should be visible at the end
  * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored
+ * ```
  *      when checking the transition. If true we will not fail the assertion if a rotation layer is
  *      visible to fill the gap between the [originalLayer] being visible and the [newLayer] being
  *      visible.
- * @param ignoreSnapshot If the snapshot layer should be ignored during the transition
+ * @param ignoreSnapshot
+ * ```
+ * If the snapshot layer should be ignored during the transition
+ * ```
  *     (useful mostly for app launch)
- * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition.
+ * @param ignoreSplashscreen
+ * ```
+ * If the splashscreen layer should be ignored during the transition.
+ * ```
  *      If true then we will allow for a splashscreen to be shown before the layer is shown,
  *      otherwise we won't and the layer must appear immediately.
+ * ```
  */
 fun FlickerTestParameter.replacesLayer(
     originalLayer: IComponentNameMatcher,
@@ -333,13 +314,7 @@
         assertion.then().isVisible(newLayer)
     }
 
-    assertLayersStart {
-        this.isVisible(originalLayer)
-            .isInvisible(newLayer)
-    }
+    assertLayersStart { this.isVisible(originalLayer).isInvisible(newLayer) }
 
-    assertLayersEnd {
-        this.isInvisible(originalLayer)
-            .isVisible(newLayer)
-    }
+    assertLayersEnd { this.isInvisible(originalLayer).isVisible(newLayer) }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index 34544ea..b23fb5a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.activityembedding
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -42,9 +41,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(
-    testSpec: FlickerTestParameter
-) : ActivityEmbeddingTestBase(testSpec) {
+class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
+    ActivityEmbeddingTestBase(testSpec) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -52,9 +50,7 @@
             tapl.setExpectedRotationCheckEnabled(false)
             testApp.launchViaIntent(wmHelper)
         }
-        transitions {
-            testApp.launchPlaceholderSplit(wmHelper)
-        }
+        transitions { testApp.launchPlaceholderSplit(wmHelper) }
         teardown {
             tapl.goHome()
             testApp.exit(wmHelper)
@@ -66,8 +62,8 @@
     fun mainActivityBecomesInvisible() {
         testSpec.assertLayers {
             isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                    .then()
-                    .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
         }
     }
 
@@ -76,72 +72,68 @@
     fun placeholderSplitBecomesVisible() {
         testSpec.assertLayers {
             isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                    .then()
-                    .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
         }
         testSpec.assertLayers {
             isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                    .then()
-                    .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
         }
     }
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
@@ -150,20 +142,21 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(
-                            supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                            supportedNavigationModes = listOf(
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                            )
-                    )
+                .getConfigNonRotationTests(
+                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
+                )
         }
     }
 }
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 ec2b4fa..b16bfe0 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
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2020 The Android Open Source Project
  *
@@ -23,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -37,35 +35,40 @@
  * To run this test: `atest FlickerTests:CloseAppBackButtonTest`
  *
  * Actions:
+ * ```
  *     Make sure no apps are running on the device
  *     Launch an app [testApp] and wait animation to complete
  *     Press back button
- *
+ * ```
  * To run only the presubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
+ * ```
  * To run only the postsubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
+ * ```
  * To run only the flaky assertions add: `--
+ * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [CloseAppTransition]
  *     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)
-@Group4
 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -73,9 +76,7 @@
             super.transition(this)
             transitions {
                 tapl.pressBack()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
         }
 
@@ -88,14 +89,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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 1322a1c..78d0860 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
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -36,52 +35,53 @@
  * To run this test: `atest FlickerTests:CloseAppHomeButtonTest`
  *
  * Actions:
+ * ```
  *     Make sure no apps are running on the device
  *     Launch an app [testApp] and wait animation to complete
  *     Press home button
- *
+ * ```
  * To run only the presubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
+ * ```
  * To run only the postsubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
+ * ```
  * To run only the flaky assertions add: `--
+ * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [CloseAppTransition]
  *     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)
-@Group4
 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                tapl.setExpectedRotationCheckEnabled(false)
-            }
+            setup { tapl.setExpectedRotationCheckEnabled(false) }
             transitions {
                 // Can't use TAPL at the moment because of rotation test issues
                 // When pressing home, TAPL expects the orientation to remain constant
                 // However, when closing a landscape app back to a portrait-only launcher
                 // this causes an error in verifyActiveContainer();
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
         }
 
@@ -94,14 +94,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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 f296d97..5bb227f 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
@@ -27,9 +27,7 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
 import org.junit.Test
 
-/**
- * Base test class for transitions that close an app back to the launcher screen
- */
+/** Base test class for transitions that close an app back to the launcher screen */
 abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
@@ -40,42 +38,30 @@
             testApp.launchViaIntent(wmHelper)
             this.setRotation(testSpec.startRotation)
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
     }
 
     /**
-     * Checks that [testApp] is the top visible app window at the start of the transition and
-     * that it is replaced by [LAUNCHER] during the transition
+     * Checks that [testApp] is the top visible app window at the start of the transition and that
+     * it is replaced by [LAUNCHER] during the transition
      */
     @Presubmit
     @Test
     open fun launcherReplacesAppWindowAsTopWindow() {
-        testSpec.assertWm {
-            this.isAppWindowOnTop(testApp)
-                .then()
-                .isAppWindowOnTop(LAUNCHER)
-        }
+        testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
     }
 
     /**
-     * Checks that [LAUNCHER] is invisible at the start of the transition and that
-     * it becomes visible during the transition
+     * Checks that [LAUNCHER] is invisible at the start of the transition and that it becomes
+     * visible during the transition
      */
     @Presubmit
     @Test
     open fun launcherWindowBecomesVisible() {
-        testSpec.assertWm {
-            this.isAppWindowNotOnTop(LAUNCHER)
-                .then()
-                .isAppWindowOnTop(LAUNCHER)
-        }
+        testSpec.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
     }
 
-    /**
-     * Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible
-     */
+    /** Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible */
     @Presubmit
     @Test
     open fun launcherLayerReplacesApp() {
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 b8fe9f9..48e1e64 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
@@ -32,13 +32,14 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume.assumeNotNull
 
-class ActivityEmbeddingAppHelper @JvmOverloads constructor(
-        instr: Instrumentation,
-        launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME,
-        component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
-        launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-                .getInstance(instr)
-                .launcherStrategy
+class ActivityEmbeddingAppHelper
+@JvmOverloads
+constructor(
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
+    component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     /**
@@ -46,14 +47,15 @@
      * placeholder secondary activity based on the placeholder rule.
      */
     fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
-        val launchButton = uiDevice.wait(
+        val launchButton =
+            uiDevice.wait(
                 Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
-                FIND_TIMEOUT)
-        require(launchButton != null) {
-            "Can't find launch placeholder split button on screen."
-        }
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch placeholder split button on screen." }
         launchButton.click()
-        wmHelper.StateSyncBuilder()
+        wmHelper
+            .StateSyncBuilder()
             .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, STATE_RESUMED)
             .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, STATE_RESUMED)
             .waitForAndVerify()
@@ -62,14 +64,15 @@
     companion object {
         private const val TAG = "ActivityEmbeddingAppHelper"
 
-        val MAIN_ACTIVITY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        val MAIN_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
 
-        val PLACEHOLDER_PRIMARY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        val PLACEHOLDER_PRIMARY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+                .toFlickerComponent()
 
-        val PLACEHOLDER_SECONDARY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME
+        val PLACEHOLDER_SECONDARY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
                 .toFlickerComponent()
 
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
index 826cc2e..4ff4e31 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -23,4 +23,4 @@
     instrumentation: Instrumentation,
     activityLabel: String,
     component: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, component)
+) : StandardAppHelper(instrumentation, activityLabel, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
index 34f9ce4..a2d4d3a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
@@ -23,12 +23,18 @@
 import android.provider.MediaStore
 import com.android.server.wm.traces.common.ComponentNameMatcher
 
-class CameraAppHelper @JvmOverloads constructor(
+class CameraAppHelper
+@JvmOverloads
+constructor(
     instrumentation: Instrumentation,
     pkgManager: PackageManager = instrumentation.context.packageManager
-) : StandardAppHelper(instrumentation, getCameraLauncherName(pkgManager),
-        getCameraComponent(pkgManager)){
-    companion object{
+) :
+    StandardAppHelper(
+        instrumentation,
+        getCameraLauncherName(pkgManager),
+        getCameraComponent(pkgManager)
+    ) {
+    companion object {
         private fun getCameraIntent(): Intent {
             return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
         }
@@ -36,13 +42,15 @@
         private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
             val intent = getCameraIntent()
             return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
-                    ?: error("unable to resolve camera activity")
+                ?: error("unable to resolve camera activity")
         }
 
         private fun getCameraComponent(pkgManager: PackageManager): ComponentNameMatcher {
             val resolveInfo = getResolveInfo(pkgManager)
-            return ComponentNameMatcher(resolveInfo.activityInfo.packageName,
-                    className = resolveInfo.activityInfo.name)
+            return ComponentNameMatcher(
+                resolveInfo.activityInfo.packageName,
+                className = resolveInfo.activityInfo.name
+            )
         }
 
         private fun getCameraLauncherName(pkgManager: PackageManager): String {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index b696fc3..4340bd7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -23,12 +23,13 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class FixedOrientationAppHelper @JvmOverloads constructor(
-     instr: Instrumentation,
-     launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
-     component: ComponentNameMatcher =
-             ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-             .getInstance(instr)
-             .launcherStrategy
- ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+class FixedOrientationAppHelper
+@JvmOverloads
+constructor(
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
+    component: ComponentNameMatcher =
+        ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
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 d08cb55..73cb862 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
@@ -15,6 +15,7 @@
  */
 
 @file:JvmName("FlickerExtensions")
+
 package com.android.server.wm.flicker.helpers
 
 import com.android.server.wm.flicker.Flicker
@@ -31,4 +32,4 @@
         instrumentation,
         clearCacheAfterParsing = false,
         wmHelper = wmHelper
-)
+    )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index ea5a5f8..d45315e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -27,11 +27,12 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-class GameAppHelper @JvmOverloads constructor(
+class GameAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-        ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+    launcherName: String = ActivityOptions.Game.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy =
         LauncherStrategyFactory.getInstance(instr).launcherStrategy,
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
@@ -42,13 +43,18 @@
      * @return true if the swipe operation is successful.
      */
     fun swipeDown(): Boolean {
-        val gameView = uiDevice.wait(
-            Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS)
+        val gameView =
+            uiDevice.wait(Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS)
         require(gameView != null) { "Mock game app view not found." }
 
         val bound = gameView.getVisibleBounds()
         return uiDevice.swipe(
-            bound.centerX(), bound.top, bound.centerX(), bound.centerY(), SWIPE_STEPS)
+            bound.centerX(),
+            bound.top,
+            bound.centerX(),
+            bound.centerY(),
+            SWIPE_STEPS
+        )
     }
 
     /**
@@ -64,21 +70,27 @@
         wmHelper: WindowManagerStateHelper,
         direction: Direction
     ): Boolean {
-        val ratioForScreenBottom = 0.97
+        val ratioForScreenBottom = 0.99
         val fullView = wmHelper.getWindowRegion(componentMatcher)
         require(!fullView.isEmpty) { "Target $componentMatcher view not found." }
 
         val bound = fullView.bounds
         val targetYPos = bound.bottom * ratioForScreenBottom
-        val endX = when (direction) {
-            Direction.LEFT -> bound.left
-            Direction.RIGHT -> bound.right
-            else -> {
-                throw IllegalStateException("Only left or right direction is allowed.")
+        val endX =
+            when (direction) {
+                Direction.LEFT -> bound.left
+                Direction.RIGHT -> bound.right
+                else -> {
+                    throw IllegalStateException("Only left or right direction is allowed.")
+                }
             }
-        }
         return uiDevice.swipe(
-            bound.centerX(), targetYPos.toInt(), endX, targetYPos.toInt(), SWIPE_STEPS)
+            bound.centerX(),
+            targetYPos.toInt(),
+            endX,
+            targetYPos.toInt(),
+            SWIPE_STEPS
+        )
     }
 
     /**
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
new file mode 100644
index 0000000..858cd76
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.helpers;
+
+import android.annotation.NonNull;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+
+/**
+ * Injects gestures given an {@link Instrumentation} object.
+ */
+public class GestureHelper {
+    // Inserted after each motion event injection.
+    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
+
+    private final UiAutomation mUiAutomation;
+
+    /**
+     * A pair of floating point values.
+     */
+    public static class Tuple {
+        public float x;
+        public float y;
+
+        public Tuple(float x, float y) {
+            this.x = x;
+            this.y = y;
+        }
+    }
+
+    public GestureHelper(Instrumentation instrumentation) {
+        mUiAutomation = instrumentation.getUiAutomation();
+    }
+
+    /**
+     * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
+     *
+     * @param startPoint1 initial coordinates of the first pointer
+     * @param startPoint2 initial coordinates of the second pointer
+     * @param endPoint1 final coordinates of the first pointer
+     * @param endPoint2 final coordinates of the second pointer
+     * @param steps number of steps to take to animate pinching
+     * @return true if gesture is injected successfully
+     */
+    public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
+            @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
+        PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
+        PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
+
+        PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
+        PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
+
+        PointerProperties[] ptrProps = new PointerProperties[] {
+                ptrProp1, ptrProp2
+        };
+
+        PointerCoords[] ptrCoords = new PointerCoords[] {
+                ptrCoord1, ptrCoord2
+        };
+
+        long downTime = SystemClock.uptimeMillis();
+
+        if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
+            return false;
+        }
+
+        if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
+            return false;
+        }
+
+        if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
+                downTime, steps)) {
+            return false;
+        }
+
+        if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
+            return false;
+        }
+
+        return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
+    }
+
+    private boolean primaryPointerDown(@NonNull PointerProperties prop,
+            @NonNull PointerCoords coord, long downTime) {
+        MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
+                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
+
+        return injectEventSync(event);
+    }
+
+    private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
+            @NonNull PointerCoords[] coords, long downTime, int index) {
+        // at least 2 pointers are needed
+        if (props.length != coords.length || coords.length < 2) {
+            return false;
+        }
+
+        long eventTime = SystemClock.uptimeMillis();
+
+        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
+                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
+
+        return injectEventSync(event);
+    }
+
+    private boolean movePointers(@NonNull PointerProperties[] props,
+            @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
+        // the number of endpoints should be the same as the number of pointers
+        if (props.length != coords.length || coords.length != endPoints.length) {
+            return false;
+        }
+
+        // prevent division by 0 and negative number of steps
+        if (steps < 1) {
+            steps = 1;
+        }
+
+        // save the starting points before updating any pointers
+        Tuple[] startPoints = new Tuple[coords.length];
+
+        for (int i = 0; i < coords.length; i++) {
+            startPoints[i] = new Tuple(coords[i].x, coords[i].y);
+        }
+
+        MotionEvent event;
+        long eventTime;
+
+        for (int i = 0; i < steps; i++) {
+            // inject a delay between movements
+            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
+
+            // update the coordinates
+            for (int j = 0; j < coords.length; j++) {
+                coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
+                coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
+            }
+
+            eventTime = SystemClock.uptimeMillis();
+
+            event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
+                    coords.length, props, coords);
+
+            boolean didInject = injectEventSync(event);
+
+            if (!didInject) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean primaryPointerUp(@NonNull PointerProperties prop,
+            @NonNull PointerCoords coord, long downTime) {
+        long eventTime = SystemClock.uptimeMillis();
+
+        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
+                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
+
+        return injectEventSync(event);
+    }
+
+    private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
+            @NonNull PointerCoords[] coords, long downTime, int index) {
+        // at least 2 pointers are needed
+        if (props.length != coords.length || coords.length < 2) {
+            return false;
+        }
+
+        long eventTime = SystemClock.uptimeMillis();
+
+        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
+                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
+
+        return injectEventSync(event);
+    }
+
+    private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
+        PointerCoords ptrCoord = new PointerCoords();
+        ptrCoord.x = x;
+        ptrCoord.y = y;
+        ptrCoord.pressure = pressure;
+        ptrCoord.size = size;
+        return ptrCoord;
+    }
+
+    private PointerProperties getPointerProp(int id, int toolType) {
+        PointerProperties ptrProp = new PointerProperties();
+        ptrProp.id = id;
+        ptrProp.toolType = toolType;
+        return ptrProp;
+    }
+
+    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
+            int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
+        return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
+                ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
+                0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
+
+    private boolean injectEventSync(InputEvent event) {
+        return mUiAutomation.injectInputEvent(event, true);
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index e01cceb..16753e6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -28,13 +28,15 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import java.util.regex.Pattern
 
-class ImeAppAutoFocusHelper @JvmOverloads constructor(
+class ImeAppAutoFocusHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
     private val rotation: Int,
     private val imePackageName: String = IME_PACKAGE,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
         // do nothing (the app is focused automatically)
@@ -52,57 +54,61 @@
     }
 
     override fun open() {
-        val expectedPackage = if (rotation.isRotated()) {
-            imePackageName
-        } else {
-            getPackage()
-        }
+        val expectedPackage =
+            if (rotation.isRotated()) {
+                imePackageName
+            } else {
+                getPackage()
+            }
         launcherStrategy.launch(appName, expectedPackage)
     }
 
     fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
-        val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
-                "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
+        val button =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "start_dialog_themed_activity_btn")),
+                FIND_TIMEOUT
+            )
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. Screen turned off)"
+                "was left in an unknown state (e.g. Screen turned off)"
         }
         button.click()
-        wmHelper.StateSyncBuilder()
-            .withFullScreenApp(
-                ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
             .waitForAndVerify()
     }
+
     fun dismissDialog(wmHelper: WindowManagerStateHelper) {
-        val dialog = uiDevice.wait(
-                Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
+        val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
 
         // Pressing back key to dismiss the dialog
         if (dialog != null) {
             uiDevice.pressBack()
-            wmHelper.StateSyncBuilder()
-                .withAppTransitionIdle()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
         }
     }
+
     fun getInsetsVisibleFromDialog(type: Int): Boolean {
-        val insetsVisibilityTextView = uiDevice.wait(
-                Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
+        val insetsVisibilityTextView =
+            uiDevice.wait(Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
         if (insetsVisibilityTextView != null) {
             val visibility = insetsVisibilityTextView.text.toString()
-            val matcher = when (type) {
-                ime() -> {
-                    Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility)
+            val matcher =
+                when (type) {
+                    ime() -> {
+                        Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility)
+                    }
+                    statusBars() -> {
+                        Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility)
+                    }
+                    navigationBars() -> {
+                        Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility)
+                    }
+                    else -> null
                 }
-                statusBars() -> {
-                    Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility)
-                }
-                navigationBars() -> {
-                    Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility)
-                }
-                else -> null
-            }
             if (matcher != null && matcher.find()) {
                 return matcher.group(1).equals("VISIBLE")
             }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index b672b1b..cefbf18 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -26,14 +26,14 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-open class ImeAppHelper @JvmOverloads constructor(
+open class ImeAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+    launcherName: String = ActivityOptions.Ime.Default.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     /**
      * Opens the IME and wait for it to be displayed
@@ -41,9 +41,8 @@
      * @param wmHelper Helper used to wait for WindowManager states
      */
     open fun openIME(wmHelper: WindowManagerStateHelper) {
-        val editText = uiDevice.wait(
-            Until.findObject(By.res(getPackage(), "plain_text_input")),
-            FIND_TIMEOUT)
+        val editText =
+            uiDevice.wait(Until.findObject(By.res(getPackage(), "plain_text_input")), FIND_TIMEOUT)
 
         requireNotNull(editText) {
             "Text field not found, this usually happens when the device " +
@@ -54,9 +53,7 @@
     }
 
     protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
-        wmHelper.StateSyncBuilder()
-            .withImeShown()
-            .waitForAndVerify()
+        wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
     }
 
     /**
@@ -66,21 +63,19 @@
      */
     open fun closeIME(wmHelper: WindowManagerStateHelper) {
         uiDevice.pressBack()
-        wmHelper.StateSyncBuilder()
-            .withImeGone()
-            .waitForAndVerify()
+        wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify()
     }
 
     open fun finishActivity(wmHelper: WindowManagerStateHelper) {
-        val finishButton = uiDevice.wait(
+        val finishButton =
+            uiDevice.wait(
                 Until.findObject(By.res(getPackage(), "finish_activity_btn")),
-                FIND_TIMEOUT)
+                FIND_TIMEOUT
+            )
         requireNotNull(finishButton) {
             "Finish activity button not found, probably IME activity is not on the screen?"
         }
         finishButton.click()
-        wmHelper.StateSyncBuilder()
-            .withActivityRemoved(this)
-            .waitForAndVerify()
+        wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
index df47e9d..871b66e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -24,33 +24,32 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
+class ImeEditorPopupDialogAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.EditorPopupDialogActivity.LABEL,
     component: ComponentNameMatcher =
-            ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.Ime.EditorPopupDialogActivity.COMPONENT.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
         val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
 
-        require(editText != null) {
+        requireNotNull(editText) {
             "Text field not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         editText.click()
         waitIMEShown(wmHelper)
     }
 
     fun dismissDialog(wmHelper: WindowManagerStateHelper) {
-        val dismissButton = uiDevice.wait(
-                Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
+        val dismissButton = uiDevice.wait(Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
 
         // Pressing back key to dismiss the dialog
         if (dismissButton != null) {
             dismissButton.click()
-            wmHelper.StateSyncBuilder()
-                .withAppTransitionIdle()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index d3945c1..1502ad5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -23,12 +23,13 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class ImeStateInitializeHelper @JvmOverloads constructor(
+class ImeStateInitializeHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
similarity index 66%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
index 4d0fbc4..ba8dabd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
 
-class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.SimpleActivity.LABEL,
-    Components.SimpleActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
+class LaunchBubbleHelper(instrumentation: Instrumentation) :
+    StandardAppHelper(
+        instrumentation,
+        ActivityOptions.Bubbles.LaunchBubble.LABEL,
+        ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent()
+    )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index 807e672..f00904b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -27,26 +27,26 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class MailAppHelper @JvmOverloads constructor(
-        instr: Instrumentation,
-        launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME,
-        component: ComponentNameMatcher =
-                ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-        launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-                .getInstance(instr)
-                .launcherStrategy
+class MailAppHelper
+@JvmOverloads
+constructor(
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.Mail.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     fun openMail(rowIdx: Int) {
-        val rowSel = By.res(getPackage(), "mail_row_item_text")
-            .textEndsWith(String.format("%04d", rowIdx))
+        val rowSel =
+            By.res(getPackage(), "mail_row_item_text").textEndsWith(String.format("%04d", rowIdx))
         var row: UiObject2? = null
         for (i in 1..1000) {
             row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS)
             if (row != null) break
             scrollDown()
         }
-        require(row != null) {""}
+        require(row != null) { "" }
         row.click()
         uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT)
     }
@@ -57,9 +57,9 @@
     }
 
     fun waitForMailList(): UiObject2 {
-        var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
+        val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
         val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT)
-        require(ret != null) {""}
+        requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" }
         return ret
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
new file mode 100644
index 0000000..e0f6fb0
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.helpers
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import java.io.IOException
+
+class MultiWindowUtils(
+    instrumentation: Instrumentation,
+    activityLabel: String,
+    componentsInfo: ComponentNameMatcher
+) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) {
+
+    companion object {
+        fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+            try {
+                SystemUtil.runShellCommand(instrumentation, cmd)
+            } catch (e: IOException) {
+                Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+            }
+        }
+
+        fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+            Settings.Global.getInt(
+                context.contentResolver,
+                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+            )
+
+        fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+            Settings.Global.putInt(
+                context.contentResolver,
+                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+                configValue
+            )
+
+        fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+            executeShellCommand(
+                instrumentation,
+                createConfigSupportsNonResizableMultiWindowCommand(configValue)
+            )
+
+        fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+            executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+        private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+            "wm set-multi-window-config --supportsNonResizable $configValue"
+
+        private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 9fb574c..34294a6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -27,27 +27,24 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-class NewTasksAppHelper @JvmOverloads constructor(
+class NewTasksAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-        ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+    launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val button = device.wait(
-            Until.findObject(By.res(getPackage(), "launch_new_task")),
-            FIND_TIMEOUT)
+        val button =
+            device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT)
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
-        wmHelper.StateSyncBuilder()
-            .withAppTransitionIdle()
-            .waitForAndVerify()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index a1dbeea..bbb782d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -23,12 +23,13 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class NonResizeableAppHelper @JvmOverloads constructor(
+class NonResizeableAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index b031a45..a3e32e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -26,27 +26,27 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-class NotificationAppHelper @JvmOverloads constructor(
+class NotificationAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-            ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+    launcherName: String = ActivityOptions.Notification.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     fun postNotification(wmHelper: WindowManagerStateHelper) {
-        val button = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "post_notification")),
-                FIND_TIMEOUT)
+        val button =
+            uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT)
 
-        require(button != null) {
+        requireNotNull(button) {
             "Post notification button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
 
         uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
-                ?: error("Flicker Notification not found")
+            ?: error("Flicker Notification not found")
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
new file mode 100644
index 0000000..19ee09a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.helpers
+
+import android.app.Instrumentation
+import android.media.session.MediaController
+import android.media.session.MediaSessionManager
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.GestureHelper.Tuple
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.region.Region
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+open class PipAppHelper(instrumentation: Instrumentation) :
+    StandardAppHelper(
+        instrumentation,
+        ActivityOptions.Pip.LABEL,
+        ActivityOptions.Pip.COMPONENT.toFlickerComponent()
+    ) {
+    private val mediaSessionManager: MediaSessionManager
+        get() =
+            context.getSystemService(MediaSessionManager::class.java)
+                ?: error("Could not get MediaSessionManager")
+
+    private val mediaController: MediaController?
+        get() =
+            mediaSessionManager.getActiveSessions(null).firstOrNull { it.packageName == `package` }
+
+    private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
+
+    open fun clickObject(resId: String) {
+        val selector = By.res(`package`, resId)
+        val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
+
+        obj.click()
+    }
+
+    /**
+     * Expands the PIP window my using the pinch out gesture.
+     *
+     * @param percent The percentage by which to increase the pip window size.
+     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
+     */
+    fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
+        // the percentage must be between 0.0f and 1.0f
+        if (percent <= 0.0f || percent > 1.0f) {
+            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
+        }
+
+        val windowRect = getWindowRect(wmHelper)
+
+        // first pointer's initial x coordinate is halfway between the left edge and the center
+        val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+        // second pointer's initial x coordinate is halfway between the right edge and the center
+        val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+
+        // horizontal distance the window should increase by
+        val distIncrease = windowRect.width * percent
+
+        // final x-coordinates
+        val finalLeftX = initLeftX - (distIncrease / 2)
+        val finalRightX = initRightX + (distIncrease / 2)
+
+        // y-coordinate is the same throughout this animation
+        val yCoord = windowRect.centerY().toFloat()
+
+        var adjustedSteps = MIN_STEPS_TO_ANIMATE
+
+        // if distance per step is at least 1, then we can use the number of steps requested
+        if (distIncrease.toInt() / (steps * 2) >= 1) {
+            adjustedSteps = steps
+        }
+
+        // if the distance per step is less than 1, carry out the animation in two steps
+        gestureHelper.pinch(
+                Tuple(initLeftX, yCoord), Tuple(initRightX, yCoord),
+                Tuple(finalLeftX, yCoord), Tuple(finalRightX, yCoord), adjustedSteps)
+
+        waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+    }
+
+    /**
+     * Launches the app through an intent instead of interacting with the launcher and waits until
+     * the app window is in PIP mode
+     */
+    @JvmOverloads
+    fun launchViaIntentAndWaitForPip(
+        wmHelper: WindowManagerStateHelper,
+        expectedWindowName: String = "",
+        action: String? = null,
+        stringExtras: Map<String, String>
+    ) {
+        launchViaIntentAndWaitShown(
+            wmHelper,
+            expectedWindowName,
+            action,
+            stringExtras,
+            waitConditions = arrayOf(WindowManagerConditionsFactory.hasPipWindow())
+        )
+
+        wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify()
+    }
+
+    /** Expand the PIP window back to full screen via intent and wait until the app is visible */
+    fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
+        launchViaIntentAndWaitShown(wmHelper)
+
+    fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
+        clickObject(ENTER_PIP_BUTTON_ID)
+
+        // Wait on WMHelper or simply wait for 3 seconds
+        wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify()
+        // when entering pip, the dismiss button is visible at the start. to ensure the pip
+        // animation is complete, wait until the pip dismiss button is no longer visible.
+        // b/176822698: dismiss-only state will be removed in the future
+        uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
+    }
+
+    fun enableEnterPipOnUserLeaveHint() {
+        clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
+    }
+
+    fun enableAutoEnterForPipActivity() {
+        clickObject(ENTER_PIP_AUTOENTER)
+    }
+
+    fun clickStartMediaSessionButton() {
+        clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
+    }
+
+    fun checkWithCustomActionsCheckbox() =
+        uiDevice
+            .findObject(By.res(`package`, WITH_CUSTOM_ACTIONS_BUTTON_ID))
+            ?.takeIf { it.isCheckable }
+            ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
+            ?: error("'With custom actions' checkbox not found")
+
+    fun pauseMedia() =
+        mediaController?.transportControls?.pause() ?: error("No active media session found")
+
+    fun stopMedia() =
+        mediaController?.transportControls?.stop() ?: error("No active media session found")
+
+    @Deprecated(
+        "Use PipAppHelper.closePipWindow(wmHelper) instead",
+        ReplaceWith("closePipWindow(wmHelper)")
+    )
+    open fun closePipWindow() {
+        closePipWindow(WindowManagerStateHelper(mInstrumentation))
+    }
+
+    private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
+        val windowRegion = wmHelper.getWindowRegion(this)
+        require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
+        return windowRegion.bounds
+    }
+
+    /** Taps the pip window and dismisses it by clicking on the X button. */
+    open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // search and interact with the dismiss button
+        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+        val dismissPipObject =
+            uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found")
+        val dismissButtonBounds = dismissPipObject.visibleBounds
+        uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
+
+        // Wait for animation to complete.
+        wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    /** Close the pip window by pressing the expand button */
+    fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // search and interact with the expand button
+        val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
+        uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
+        val expandPipObject =
+            uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found")
+        val expandButtonBounds = expandPipObject.visibleBounds
+        uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
+        wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify()
+    }
+
+    /** Double click on the PIP window to expand it */
+    fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        Log.d(TAG, "First click")
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        Log.d(TAG, "Second click")
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        Log.d(TAG, "Wait for app transition to end")
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+        waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+    }
+
+    private fun waitForPipWindowToExpandFrom(
+        wmHelper: WindowManagerStateHelper,
+        windowRect: Region
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("pipWindowExpanded") {
+                val pipAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        this.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val pipRegion = pipAppWindow.frameRegion
+                return@add pipRegion.coversMoreThan(windowRect)
+            }
+            .waitForAndVerify()
+    }
+
+    companion object {
+        private const val TAG = "PipAppHelper"
+        private const val ENTER_PIP_BUTTON_ID = "enter_pip"
+        private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
+        private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
+        private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
+        private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
+        // minimum number of steps to take, when animating gestures, needs to be 2
+        // so that there is at least a single intermediate layer that flicker tests can check
+        private const val MIN_STEPS_TO_ANIMATE = 2
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index 6d466d7..c904352 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -23,12 +23,13 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class SeamlessRotationAppHelper @JvmOverloads constructor(
+class SeamlessRotationAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index 804ab38..de152cb5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -23,12 +23,13 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class ShowWhenLockedAppHelper @JvmOverloads constructor(
+class ShowWhenLockedAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
     component: ComponentNameMatcher =
-            ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index 5da273a7..e415990 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -23,12 +23,12 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
-class SimpleAppHelper @JvmOverloads constructor(
+class SimpleAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-        ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+    launcherName: String = ActivityOptions.SimpleActivity.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 060e9af..1330190 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -27,18 +27,19 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
-class TwoActivitiesAppHelper @JvmOverloads constructor(
+class TwoActivitiesAppHelper
+@JvmOverloads
+constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     private val secondActivityComponent =
-        ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
 
     fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
         val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
@@ -46,14 +47,12 @@
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
 
         device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT)
-        wmHelper.StateSyncBuilder()
-            .withFullScreenApp(secondActivityComponent)
-            .waitForAndVerify()
+        wmHelper.StateSyncBuilder().withFullScreenApp(secondActivityComponent).waitForAndVerify()
     }
 
     companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 479ac8e..1a49595 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -38,10 +37,10 @@
  * Test IME window closing back to app window transitions.
  *
  * This test doesn't work on 90 degrees. According to the InputMethodService documentation:
- *
+ * ```
  *     Don't show if this is not explicitly requested by the user and the input method
  *     is fullscreen. That would be too disruptive.
- *
+ * ```
  * More details on b/190352379
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
@@ -50,57 +49,40 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
 class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            testApp.launchViaIntent(wmHelper)
-        }
-        teardown {
-            testApp.exit(wmHelper)
-        }
-        transitions {
-            testApp.closeIME(wmHelper)
-        }
+        setup { testApp.launchViaIntent(wmHelper) }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.closeIME(wmHelper) }
     }
 
     @Presubmit
     @Test
     fun imeAppWindowIsAlwaysVisible() {
-        testSpec.assertWm {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeLayerVisibleStart() {
-        testSpec.assertLayersStart {
-            this.isVisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun imeLayerInvisibleEnd() {
-        testSpec.assertLayersEnd {
-            this.isInvisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 
     companion object {
@@ -109,12 +91,13 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        // b/190352379 (IME doesn't show on app launch in 90 degrees)
+                    // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 07b52a6..463efe8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -38,10 +37,10 @@
  * Test IME window closing back to app window transitions.
  *
  * This test doesn't work on 90 degrees. According to the InputMethodService documentation:
- *
+ * ```
  *     Don't show if this is not explicitly requested by the user and the input method
  *     is fullscreen. That would be too disruptive.
- *
+ * ```
  * More details on b/190352379
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
@@ -50,7 +49,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
 class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
@@ -60,56 +58,37 @@
             tapl.setExpectedRotationCheckEnabled(false)
             testApp.launchViaIntent(wmHelper)
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
         transitions {
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .withImeGone()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().withImeGone().waitForAndVerify()
         }
     }
 
     @Presubmit
     @Test
     fun imeAppWindowBecomesInvisible() {
-        testSpec.assertWm {
-            this.isAppWindowOnTop(testApp)
-                .then()
-                .isAppWindowNotOnTop(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeLayerVisibleStart() {
-        testSpec.assertLayersStart {
-            this.isVisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun imeLayerInvisibleEnd() {
-        testSpec.assertLayersEnd {
-            this.isInvisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerBecomesInvisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-                    .then()
-                    .isInvisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
     companion object {
@@ -120,9 +99,11 @@
                 .getConfigNonRotationTests(
                     // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index fec7727..91d9a1f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
 import com.android.server.wm.flicker.traces.region.RegionSubject
@@ -39,7 +38,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
 class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
@@ -52,47 +50,20 @@
         }
         transitions {
             imeTestApp.dismissDialog(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withImeGone()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify()
         }
         teardown {
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             imeTestApp.exit(wmHelper)
         }
     }
 
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    @Postsubmit
+    @Presubmit
     @Test
     fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun imeLayerAndImeSnapshotVisibleOnScreen() {
         testSpec.assertLayers {
@@ -105,20 +76,23 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun imeSnapshotAssociatedOnAppVisibleRegion() {
         testSpec.assertLayers {
             this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
-                val imeSnapshotLayers = it.subjects.filter { subject ->
-                    subject.name.contains(
-                        ComponentNameMatcher.IME_SNAPSHOT.toLayerName()
-                    ) && subject.isVisible
-                }
+                val imeSnapshotLayers =
+                    it.subjects.filter { subject ->
+                        subject.name.contains(ComponentNameMatcher.IME_SNAPSHOT.toLayerName()) &&
+                            subject.isVisible
+                    }
                 if (imeSnapshotLayers.isNotEmpty()) {
-                    val visibleAreas = imeSnapshotLayers.mapNotNull { imeSnapshotLayer ->
-                        imeSnapshotLayer.layer?.visibleRegion
-                    }.toTypedArray()
+                    val visibleAreas =
+                        imeSnapshotLayers
+                            .mapNotNull { imeSnapshotLayer ->
+                                imeSnapshotLayer.layer?.visibleRegion
+                            }
+                            .toTypedArray()
                     val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
                     val appVisibleRegion = it.visibleRegion(imeTestApp)
                     if (imeVisibleRegion.region.isNotEmpty) {
@@ -135,7 +109,8 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    supportedNavigationModes = listOf(
+                    supportedNavigationModes =
+                    listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     ),
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 50596f1..b9c875a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
@@ -36,14 +35,13 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window closing back to app window transitions.
- * To run this test: `atest FlickerTests:CloseImeWindowToAppTest`
+ * 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)
-@Group2
 class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppHelper(instrumentation)
 
@@ -53,12 +51,8 @@
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
-        transitions {
-            testApp.closeIME(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.closeIME(wmHelper) }
     }
 
     /** {@inheritDoc} */
@@ -66,10 +60,13 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         testSpec.assertWm {
-            this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(
-                ComponentNameMatcher.IME,
-                ComponentNameMatcher.SPLASH_SCREEN,
-                ComponentNameMatcher.SNAPSHOT))
+            this.visibleWindowsShownMoreThanOneConsecutiveEntry(
+                listOf(
+                    ComponentNameMatcher.IME,
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT
+                )
+            )
         }
     }
 
@@ -90,32 +87,25 @@
         testSpec.navBarLayerPositionAtStartAndEnd()
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerIsAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeAppWindowIsAlwaysVisible() {
-        testSpec.assertWm {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 17eb8f9..1dc3ca5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -35,14 +34,13 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window closing to home transitions.
- * To run this test: `atest FlickerTests:CloseImeWindowToHomeTest`
+ * 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)
-@Group2
 class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppHelper(instrumentation)
 
@@ -55,14 +53,9 @@
         }
         transitions {
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .withImeGone()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().withImeGone().waitForAndVerify()
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
     }
 
     /** {@inheritDoc} */
@@ -86,40 +79,25 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
-                listOf(
-                    ComponentNameMatcher.IME,
-                    ComponentNameMatcher.SPLASH_SCREEN
-                )
+                listOf(ComponentNameMatcher.IME, ComponentNameMatcher.SPLASH_SCREEN)
             )
         }
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
 
-    @Presubmit
-    @Test
-    fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+    @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppWindowBecomesInvisible() {
-        testSpec.assertWm {
-            this.isAppWindowVisible(testApp)
-                .then()
-                .isAppWindowInvisible(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeAppLayerBecomesInvisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-                .then()
-                .isInvisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
     companion object {
@@ -128,11 +106,12 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedRotations = listOf(Surface.ROTATION_0),
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
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 9c99d96..e0c5edc 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
@@ -15,6 +15,7 @@
  */
 
 @file:JvmName("CommonAssertions")
+
 package com.android.server.wm.flicker.ime
 
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -22,17 +23,13 @@
 
 fun FlickerTestParameter.imeLayerBecomesVisible() {
     assertLayers {
-        this.isInvisible(ComponentNameMatcher.IME)
-            .then()
-            .isVisible(ComponentNameMatcher.IME)
+        this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
     }
 }
 
 fun FlickerTestParameter.imeLayerBecomesInvisible() {
     assertLayers {
-        this.isVisible(ComponentNameMatcher.IME)
-            .then()
-            .isInvisible(ComponentNameMatcher.IME)
+        this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
     }
 }
 
@@ -46,9 +43,7 @@
                 .isNonAppWindowVisible(ComponentNameMatcher.IME)
         }
     } else {
-        assertWm {
-            this.isNonAppWindowVisible(ComponentNameMatcher.IME)
-        }
+        assertWm { this.isNonAppWindowVisible(ComponentNameMatcher.IME) }
     }
 }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index f0776c1..073da4f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -47,30 +47,22 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+class LaunchAppShowImeAndDialogThemeAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
             testApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withImeShown()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
             testApp.startDialogThemedActivity(wmHelper)
             // Verify IME insets isn't visible on dialog since it's non-IME focusable window
             assertFalse(testApp.getInsetsVisibleFromDialog(ime()))
             assertTrue(testApp.getInsetsVisibleFromDialog(statusBars()))
             assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars()))
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
-        transitions {
-            testApp.dismissDialog(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.dismissDialog(wmHelper) }
     }
 
     /** {@inheritDoc} */
@@ -83,41 +75,29 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
-    /**
-     * Checks that [ComponentMatcher.IME] layer becomes visible during the transition
-     */
-    @Presubmit
-    @Test
-    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+    /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
+    @Presubmit @Test fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
 
-    /**
-     * Checks that [ComponentMatcher.IME] layer is visible at the end of the transition
-     */
+    /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
     @Presubmit
     @Test
     fun imeLayerExistsEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
     }
 
-    /**
-     * Checks that [ComponentMatcher.IME_SNAPSHOT] layer is invisible always.
-     */
+    /** Checks that [ComponentMatcher.IME_SNAPSHOT] layer is invisible always. */
     @Presubmit
     @Test
     fun imeSnapshotNotVisible() {
-        testSpec.assertLayers {
-            this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT)
-        }
+        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
@@ -125,10 +105,11 @@
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
                     supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 85e9e75..a93f176 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -41,27 +41,33 @@
  * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest`
  *
  * Actions:
+ * ```
  *     Make sure no apps are running on the device
  *     Launch an app [testApp] that automatically displays IME and wait animation to complete
- *
+ * ```
  * To run only the presubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
+ * ```
  * To run only the postsubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
+ * ```
  * To run only the flaky assertions add: `--
+ * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [CloseAppTransition]
  *     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)
@@ -83,65 +89,48 @@
         }
         transitions {
             testApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withImeShown()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
         }
     }
 
-    /**
-     * Checks that [ComponentMatcher.IME] window becomes visible during the transition
-     */
-    @Presubmit
-    @Test
-    fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+    /** Checks that [ComponentMatcher.IME] window becomes visible during the transition */
+    @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
 
-    /**
-     * Checks that [ComponentMatcher.IME] layer becomes visible during the transition
-     */
-    @Presubmit
-    @Test
-    fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+    /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
+    @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
 
-    /**
-     * Checks that [ComponentMatcher.IME] layer is invisible at the start of the transition
-     */
+    /** Checks that [ComponentMatcher.IME] layer is invisible at the start of the transition */
     @Presubmit
     @Test
     fun imeLayerNotExistsStart() {
-        testSpec.assertLayersStart {
-            this.isInvisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    /**
-     * Checks that [ComponentMatcher.IME] layer is visible at the end of the transition
-     */
+    /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
     @Presubmit
     @Test
     fun imeLayerExistsEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedRotations = listOf(Surface.ROTATION_0),
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index d42a6c3..a6bd791 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -24,10 +25,11 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,7 +46,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
 class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val testApp = ImeAppHelper(instrumentation)
@@ -56,21 +57,27 @@
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
         }
-        transitions {
-            testApp.finishActivity(wmHelper)
-        }
-        teardown {
-            simpleApp.exit(wmHelper)
-        }
+        transitions { testApp.finishActivity(wmHelper) }
+        teardown { simpleApp.exit(wmHelper) }
     }
 
-    @Presubmit
-    @Test
-    fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+    @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+
+    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
-    fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 246284124)
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
@@ -78,11 +85,12 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedRotations = listOf(Surface.ROTATION_0),
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 5f025f9..3e18d59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -25,11 +25,12 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
+import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,17 +39,15 @@
 
 /**
  * Test IME window layer will become visible when switching from the fixed orientation activity
- * (e.g. Launcher activity).
- * To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
+ * (e.g. Launcher activity). To run this test: `atest
+ * FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class OpenImeWindowFromFixedOrientationAppTest(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+class OpenImeWindowFromFixedOrientationAppTest(testSpec: FlickerTestParameter) :
+    BaseTest(testSpec) {
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     /** {@inheritDoc} */
@@ -61,18 +60,14 @@
 
             // Swiping out the IME activity to home.
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
         }
         transitions {
             // Bring the exist IME activity to the front in landscape mode device rotation.
             setRotation(Surface.ROTATION_90)
             imeTestApp.launchViaIntent(wmHelper)
         }
-        teardown {
-            imeTestApp.exit(wmHelper)
-        }
+        teardown { imeTestApp.exit(wmHelper) }
     }
 
     /** {@inheritDoc} */
@@ -96,6 +91,14 @@
     @Postsubmit
     @Test
     fun snapshotStartingWindowLayerCoversExactlyOnApp() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+    }
+
+    @Presubmit
+    @Test
+    fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
         testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
@@ -103,18 +106,17 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_90),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedRotations = listOf(Surface.ROTATION_90),
+                    supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 84b0f60..b43efea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
@@ -33,54 +32,38 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/**
- * Test IME window opening transitions.
- * To run this test: `atest FlickerTests:OpenImeWindowTest`
- */
+/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
 class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            testApp.launchViaIntent(wmHelper)
-        }
-        transitions {
-            testApp.openIME(wmHelper)
-        }
+        setup { testApp.launchViaIntent(wmHelper) }
+        transitions { testApp.openIME(wmHelper) }
         teardown {
             testApp.closeIME(wmHelper)
             testApp.exit(wmHelper)
         }
     }
 
-    @Presubmit
-    @Test
-    fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+    @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
 
     @Presubmit
     @Test
     fun appWindowAlwaysVisibleOnTop() {
-        testSpec.assertWm {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+    @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
 
     @Presubmit
     @Test
     fun layerAlwaysVisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isVisible(testApp) }
     }
 
     companion object {
@@ -89,11 +72,12 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    supportedRotations = listOf(Surface.ROTATION_0),
+                    supportedNavigationModes =
+                        listOf(
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                        )
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index f4d8f5b..0a7701e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -25,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
@@ -43,48 +41,42 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window layer will be associated with the app task when going to the overview screen.
- * To run this test: `atest FlickerTests:OpenImeWindowToOverViewTest`
+ * 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)
-@Group4
 class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            imeTestApp.launchViaIntent(wmHelper)
-        }
+        setup { imeTestApp.launchViaIntent(wmHelper) }
         transitions {
             device.pressRecentApps()
-            val builder = wmHelper.StateSyncBuilder()
-                .withRecentsActivityVisible()
+            val builder = wmHelper.StateSyncBuilder().withRecentsActivityVisible()
             waitNavStatusBarVisibility(builder)
             builder.waitForAndVerify()
         }
         teardown {
             device.pressHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             imeTestApp.exit(wmHelper)
         }
     }
 
     /**
-     * The bars (including [ComponentMatcher.STATUS_BAR] and [ComponentMatcher.NAV_BAR]) are
+     * The bars (including [ComponentNameMatcher.STATUS_BAR] and [ComponentNameMatcher.NAV_BAR]) are
      * expected to be hidden while entering overview in landscape if launcher is set to portrait
      * only. Because "showing portrait overview (launcher) in landscape display" is an intermediate
      * state depending on the touch-up to decide the intention of gesture, the display may keep in
      * landscape if return to app, or change to portrait if the gesture is to swipe-to-home.
      *
-     * So instead of showing landscape bars with portrait launcher at the same time
-     * (especially return-to-home that launcher workspace becomes visible), hide the bars until
-     * leave overview to have cleaner appearance.
+     * So instead of showing landscape bars with portrait launcher at the same time (especially
+     * return-to-home that launcher workspace becomes visible), hide the bars until leave overview
+     * to have cleaner appearance.
      *
      * b/227189877
      */
@@ -92,8 +84,7 @@
         when {
             testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isTablet ->
                 stateSync.add(WindowManagerConditionsFactory.isStatusBarVisible().negate())
-            else ->
-                stateSync.withNavOrTaskBarVisible().withStatusBarVisible()
+            else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible()
         }
     }
 
@@ -111,9 +102,7 @@
         testSpec.navBarLayerIsVisibleAtStartAndEnd()
     }
 
-    /**
-     * Bars are expected to be hidden while entering overview in landscape (b/227189877)
-     */
+    /** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtStartAndEndGestural() {
@@ -124,8 +113,8 @@
     }
 
     /**
-     * In the legacy transitions, the nav bar is not marked as invisible.
-     * In the new transitions this is fixed and the nav bar shows as invisible
+     * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
+     * this is fixed and the nav bar shows as invisible
      */
     @Presubmit
     @Test
@@ -134,17 +123,13 @@
         Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(testSpec.isGesturalNavigation)
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersStart {
-            this.isVisible(ComponentNameMatcher.NAV_BAR)
-        }
-        testSpec.assertLayersEnd {
-            this.isInvisible(ComponentNameMatcher.NAV_BAR)
-        }
+        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
     }
 
     /**
-     * In the legacy transitions, the nav bar is not marked as invisible.
-     * In the new transitions this is fixed and the nav bar shows as invisible
+     * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
+     * this is fixed and the nav bar shows as invisible
      */
     @Presubmit
     @Test
@@ -152,17 +137,13 @@
         Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(testSpec.isGesturalNavigation)
         Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertLayersStart {
-            this.isVisible(ComponentNameMatcher.STATUS_BAR)
-        }
-        testSpec.assertLayersEnd {
-            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /**
-     * In the legacy transitions, the nav bar is not marked as invisible.
-     * In the new transitions this is fixed and the nav bar shows as invisible
+     * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
+     * this is fixed and the nav bar shows as invisible
      */
     @Presubmit
     @Test
@@ -176,27 +157,30 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun navBarLayerIsVisibleAtStartAndEnd() { }
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+    }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun navBarLayerPositionAtStartAndEnd() { }
+    override fun navBarLayerPositionAtStartAndEnd() {
+    }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun statusBarLayerPositionAtStartAndEnd() { }
+    override fun statusBarLayerPositionAtStartAndEnd() {
+    }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() { }
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+    }
 
-    @Postsubmit
+    @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     @Presubmit
     @Test
@@ -211,12 +195,8 @@
         Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
         Assume.assumeFalse(testSpec.isTablet)
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersStart {
-            this.isVisible(ComponentNameMatcher.STATUS_BAR)
-        }
-        testSpec.assertLayersEnd {
-            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     @Presubmit
@@ -232,11 +212,9 @@
     @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
         testSpec.assertLayersStart {
-            isVisible(ComponentNameMatcher.IME).visibleRegion(ComponentNameMatcher.IME)
-                .coversAtMost(
-                    isVisible(imeTestApp)
-                        .visibleRegion(imeTestApp).region
-                )
+            isVisible(ComponentNameMatcher.IME)
+                .visibleRegion(ComponentNameMatcher.IME)
+                .coversAtMost(isVisible(imeTestApp).visibleRegion(imeTestApp).region)
         }
         testSpec.assertLayers {
             this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
@@ -254,8 +232,8 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
@@ -263,7 +241,8 @@
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
                     supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                    supportedNavigationModes = listOf(
+                    supportedNavigationModes =
+                    listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f7e5b23..a27b2da 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -16,21 +16,18 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.FlakyTest
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.ComponentNameMatcher
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,42 +35,33 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window opening transitions.
- * To run this test: `atest FlickerTests:ReOpenImeWindowTest`
+ * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
 open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-                testApp.launchViaIntent(wmHelper)
-                testApp.openIME(wmHelper)
-                this.setRotation(testSpec.startRotation)
-                device.pressRecentApps()
-                wmHelper.StateSyncBuilder()
-                    .withRecentsActivityVisible()
-                    .waitForAndVerify()
+            testApp.launchViaIntent(wmHelper)
+            testApp.openIME(wmHelper)
+            this.setRotation(testSpec.startRotation)
+            device.pressRecentApps()
+            wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
         }
         transitions {
             device.reopenAppFromOverview(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(testApp)
-                .withImeShown()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).withImeShown().waitForAndVerify()
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         // depends on how much of the animation transactions are sent to SF at once
@@ -81,94 +69,63 @@
         val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
-                listOf(ComponentNameMatcher.SPLASH_SCREEN,
-                    ComponentNameMatcher.SNAPSHOT, recentTaskComponent)
+                listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    recentTaskComponent
+                )
             )
         }
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
-                    ignoreWindows = listOf(ComponentNameMatcher.SPLASH_SCREEN,
+                ignoreWindows =
+                    listOf(
+                        ComponentNameMatcher.SPLASH_SCREEN,
                         ComponentNameMatcher.SNAPSHOT,
-                        component)
+                        component
+                    )
             )
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun launcherWindowBecomesInvisible() {
         testSpec.assertWm {
             this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
-    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
+    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
-    fun imeAppWindowVisibilityLegacy() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        // the app starts visible in live tile, and stays visible for the duration of entering
-        // and exiting overview. However, legacy transitions seem to have a bug which causes
-        // everything to restart during the test, so expect the app to disappear and come back.
-        // Since we log 1x per frame, sometimes the activity visibility and the app visibility
-        // are updated together, sometimes not, thus ignore activity check at the start
-        testSpec.assertWm {
-            this.isAppWindowVisible(testApp)
-                    .then()
-                    .isAppWindowInvisible(testApp)
-                    .then()
-                    .isAppWindowVisible(testApp)
-        }
-    }
-
-    @Presubmit
-    @Test
-    fun imeAppWindowIsAlwaysVisibleShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun imeAppWindowIsAlwaysVisible() {
         // the app starts visible in live tile, and stays visible for the duration of entering
         // and exiting overview. Since we log 1x per frame, sometimes the activity visibility
         // and the app visibility are updated together, sometimes not, thus ignore activity
         // check at the start
-        testSpec.assertWm {
-            this.isAppWindowVisible(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowVisible(testApp) }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
-    fun imeLayerIsBecomesVisibleLegacy() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.assertLayers {
-            this.isVisible(ComponentNameMatcher.IME)
-                    .then()
-                    .isInvisible(ComponentNameMatcher.IME)
-                    .then()
-                    .isVisible(ComponentNameMatcher.IME)
-        }
+    fun imeLayerBecomesVisible() {
+        testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesVisibleShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayers {
-            this.isVisible(ComponentNameMatcher.IME)
-        }
-    }
-
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun appLayerReplacesLauncher() {
         testSpec.assertLayers {
@@ -180,14 +137,66 @@
         }
     }
 
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                                        supportedRotations = listOf(Surface.ROTATION_0)
-                )
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index b75183d..0ca6457 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
@@ -25,7 +26,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -41,18 +41,15 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME windows switching with 2-Buttons or gestural navigation.
- * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ * 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)
-@Group4
 @Presubmit
-open class SwitchImeWindowsFromGestureNavTest(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = SimpleAppHelper(instrumentation)
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
@@ -65,42 +62,35 @@
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
             tapl.setExpectedRotationCheckEnabled(false)
+            tapl.setIgnoreTaskbarVisibility(true)
             this.setRotation(testSpec.startRotation)
             testApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(testApp)
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
 
             imeTestApp.launchViaIntent(wmHelper)
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(imeTestApp)
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(imeTestApp).waitForAndVerify()
 
             imeTestApp.openIME(wmHelper)
         }
         teardown {
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             testApp.exit(wmHelper)
             imeTestApp.exit(wmHelper)
         }
         transitions {
-            // [Step1]: Swipe right from imeTestApp to testApp task
+            // [Step1]: Swipe right from testApp task to imeTestApp
             createTag(TAG_IME_VISIBLE)
+            // Expect taskBar invisible when switching to imeTestApp on the large screen device.
             tapl.launchedAppState.quickSwitchToPreviousApp()
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(testApp)
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
             createTag(TAG_IME_INVISIBLE)
         }
         transitions {
-            // [Step2]: Swipe left to back to imeTestApp task
+            // [Step2]: Swipe left to back to testApp task
+            // Expect taskBar visible when switching to testApp on the large screen device.
             tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(imeTestApp)
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(imeTestApp).waitForAndVerify()
         }
     }
 
@@ -110,9 +100,7 @@
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -138,8 +126,7 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -153,6 +140,7 @@
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
+    @Presubmit
     @Test
     fun imeAppWindowVisibility() {
         testSpec.assertWm {
@@ -168,24 +156,18 @@
         }
     }
 
+    @FlakyTest(bugId = 244414110)
     @Test
     open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
-        testSpec.assertLayersStart {
-            isVisible(ComponentNameMatcher.IME)
-        }
-        testSpec.assertLayersTag(TAG_IME_VISIBLE) {
-            isVisible(ComponentNameMatcher.IME)
-        }
-        testSpec.assertLayersEnd {
-            isVisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
+        testSpec.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
+        testSpec.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
     }
 
+    @Presubmit
     @Test
     fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
-        testSpec.assertLayersTag(TAG_IME_INVISIBLE) {
-            isInvisible(ComponentNameMatcher.IME)
-        }
+        testSpec.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
     }
 
     companion object {
@@ -194,9 +176,8 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    ),
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
                     supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 5ac1712..80ab016 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -16,12 +16,10 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
@@ -34,37 +32,35 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME windows switching with 2-Buttons or gestural navigation.
- * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ * 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)
-@Group4
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(
-    testSpec: FlickerTestParameter
-) : SwitchImeWindowsFromGestureNavTest(testSpec) {
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) :
+    SwitchImeWindowsFromGestureNavTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
 
-    @FlakyTest(bugId = 228012334)
+    @Presubmit
     @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
-    @FlakyTest(bugId = 228012334)
+    @Presubmit
     @Test
     override fun imeLayerIsVisibleWhenSwitchingToImeApp() =
         super.imeLayerIsVisibleWhenSwitchingToImeApp()
 
-    @FlakyTest(bugId = 228012334)
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
-    @FlakyTest(bugId = 228012334)
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -75,8 +71,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
-     * and end of the WM trace
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
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
index 08d7be2..49bf86d0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -41,21 +40,23 @@
  * 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)
-@Group4
 class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
 
@@ -65,15 +66,11 @@
             tapl.setExpectedRotation(testSpec.startRotation)
             testApp.launchViaIntent(wmHelper)
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
         transitions {
             testApp.openSecondActivity(device, wmHelper)
             tapl.pressBack()
-            wmHelper.StateSyncBuilder()
-                .withFullScreenApp(testApp)
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
         }
     }
 
@@ -83,19 +80,17 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at
-     * the start of the transition, that
-     * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the
-     * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible
-     * at the end
+     * 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
-            .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
-        val imeAutoFocusActivityComponent = ActivityOptions
-            .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        val buttonActivityComponent =
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        val imeAutoFocusActivityComponent =
+            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
         testSpec.assertWm {
             this.isAppWindowOnTop(buttonActivityComponent)
                 .then()
@@ -106,20 +101,18 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] window is not on top. The launcher cannot be
+     * 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() {
-        testSpec.assertWm {
-            this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
-        }
+        testSpec.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible during the transition
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
      */
     @Presubmit
     @Test
@@ -131,14 +124,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 5eba78c..d0d7bbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -16,17 +16,16 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.CameraAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+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
@@ -36,18 +35,21 @@
  *
  * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
  *
- * Notes:
- * Some default assertions are inherited [OpenAppTransition]
+ * Notes: Some default assertions are inherited [OpenAppTransition]
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-open class OpenAppAfterCameraTest(
-    testSpec: FlickerTestParameter
-) : OpenAppFromLauncherTransition(testSpec) {
-    private val cameraApp = CameraAppHelper(instrumentation) /** {@inheritDoc} */
+open class OpenAppAfterCameraTest(testSpec: FlickerTestParameter) :
+    OpenAppFromLauncherTransition(testSpec) {
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
+    private val cameraApp = CameraAppHelper(instrumentation)
+    /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -59,125 +61,21 @@
                 // 2. Press home button (button nav mode) / swipe up to home (gesture nav mode)
                 tapl.goHome()
             }
-            teardown {
-                testApp.exit(wmHelper)
-            }
-            transitions {
-                testApp.launchViaIntent(wmHelper)
-            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
         }
 
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun focusChanges() = super.focusChanges()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-            super.appWindowReplacesLauncherAsTopWindow()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-            super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-            super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-            super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
new file mode 100644
index 0000000..cb61e35
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+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 (with shell transitions)
+ *
+ * To run this test: `atest FlickerTests:OpenAppAfterCameraTest_ShellTransit`
+ *
+ * Notes: Some default assertions are inherited [OpenAppTransition]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) :
+    OpenAppAfterCameraTest(testSpec) {
+    @Before
+    override fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
+    @FlakyTest
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+
+    @FlakyTest
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 9b1c541..7ccfeb7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
@@ -38,25 +37,26 @@
  * 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)
-@Group1
-class OpenAppColdFromIcon(
-    testSpec: FlickerTestParameter
-) : OpenAppFromLauncherTransition(testSpec) {
+class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
+    OpenAppFromLauncherTransition(testSpec) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -71,21 +71,17 @@
                 this.setRotation(testSpec.startRotation)
             }
             transitions {
-                tapl.goHome()
+                tapl
+                    .goHome()
                     .switchToAllApps()
                     .getAppIcon(testApp.launcherName)
                     .launch(testApp.`package`)
             }
-            teardown {
-                testApp.exit(wmHelper)
-            }
+            teardown { testApp.exit(wmHelper) }
         }
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowAsTopWindowAtEnd() =
-        super.appWindowAsTopWindowAtEnd()
+    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -94,35 +90,22 @@
         super.appWindowReplacesLauncherAsTopWindow()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() =
-        super.appLayerBecomesVisible()
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun focusChanges() = super.focusChanges()
+    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -152,8 +135,7 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -179,22 +161,19 @@
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 2c77668..7cd8526 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
@@ -39,26 +38,27 @@
  * 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)
-@Group1
-open class OpenAppColdTest(
-    testSpec: FlickerTestParameter
-) : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(testSpec: FlickerTestParameter) :
+    OpenAppFromLauncherTransition(testSpec) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -67,24 +67,17 @@
                 removeAllTasksButHome()
                 this.setRotation(testSpec.startRotation)
             }
-            teardown {
-                testApp.exit(wmHelper)
-            }
-            transitions {
-                testApp.launchViaIntent(wmHelper)
-            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
         }
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 240238245)
@@ -96,14 +89,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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 bece406..23748be 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
@@ -22,32 +22,27 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
-/**
- * Base class for app launch tests
- */
-abstract class OpenAppFromLauncherTransition(
-    testSpec: FlickerTestParameter
-) : OpenAppTransition(testSpec) {
+/** Base class for app launch tests */
+abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
+    OpenAppTransition(testSpec) {
 
-    /**
-     * Checks that the focus changes from the [ComponentMatcher.LAUNCHER] to [testApp]
-     */
+    /** Checks that the focus changes from the [ComponentMatcher.LAUNCHER] to [testApp] */
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog {
-            this.focusChanges("NexusLauncherActivity", testApp.`package`)
-        }
+        testSpec.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
     }
 
     /**
-     * Checks that [ComponentMatcher.LAUNCHER] layer is visible at the start of the transition,
-     * and is replaced by [testApp], which remains visible until the end
+     * Checks that [ComponentMatcher.LAUNCHER] layer is visible at the start of the transition, and
+     * is replaced by [testApp], which remains visible until the end
      */
     open fun appLayerReplacesLauncher() {
         testSpec.replacesLayer(
-            ComponentNameMatcher.LAUNCHER, testApp,
-            ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true,
+            ComponentNameMatcher.LAUNCHER,
+            testApp,
+            ignoreEntriesWithRotationLayer = true,
+            ignoreSnapshot = true,
             ignoreSplashscreen = true
         )
     }
@@ -64,21 +59,15 @@
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowOnTop(
-                    testApp
-                        .or(ComponentNameMatcher.SNAPSHOT)
-                        .or(ComponentNameMatcher.SPLASH_SCREEN)
+                    testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
                 )
         }
     }
 
-    /**
-     * Checks that [testApp] window is the top window at the en dof the trace
-     */
+    /** Checks that [testApp] window is the top window at the en dof the trace */
     @Presubmit
     @Test
     open fun appWindowAsTopWindowAtEnd() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 }
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
index b70bdd7..2469fae 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
@@ -45,7 +44,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 @Postsubmit
 open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
     OpenAppFromNotificationCold(testSpec) {
@@ -56,18 +54,14 @@
         get() = {
             // Needs to run at start of transition,
             // so before the transition defined in super.transition
-            transitions {
-                device.wakeUp()
-            }
+            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()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
             }
         }
 
@@ -88,13 +82,9 @@
     override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
     /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun navBarLayerPositionAtStartAndEnd() { }
+    @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {}
 
-    /**
-     * Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition
-     */
+    /** Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition */
     @Postsubmit
     @Test
     fun navBarLayerPositionAtEnd() {
@@ -105,15 +95,13 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Display is off at the start")
-    override fun statusBarLayerPositionAtStartAndEnd() { }
+    override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /**
      * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
-    @Postsubmit
-    @Test
-    fun statusBarLayerPositionEnd() = testSpec.statusBarLayerPositionAtEnd()
+    @Postsubmit @Test fun statusBarLayerPositionEnd() = testSpec.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -126,9 +114,7 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -136,9 +122,7 @@
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -153,23 +137,19 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowIsTopWindowAtEnd() =
-        super.appWindowIsTopWindowAtEnd()
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 48602c4..c26b665 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
@@ -49,7 +48,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 @Postsubmit
 open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
     OpenAppFromNotificationWarm(testSpec) {
@@ -60,49 +58,40 @@
         get() = {
             // Needs to run at start of transition,
             // so before the transition defined in super.transition
-            transitions {
-                device.wakeUp()
-            }
+            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()
+                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.
+     * 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
     @Postsubmit
     open fun appWindowBecomesFirstAndOnlyTopWindow() {
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                    .then()
-                    .isAppWindowOnTop(testApp)
+                .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
-     */
+    /** Checks that the screen is locked at the start of the transition */
     @Test
     @Postsubmit
     fun screenLockedStart() {
-        testSpec.assertWmStart {
-            isKeyguardShowing()
-        }
+        testSpec.assertWmStart { isKeyguardShowing() }
     }
 
     /** {@inheritDoc} */
@@ -119,11 +108,9 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() { }
+    override fun navBarLayerPositionAtStartAndEnd() {}
 
-    /**
-     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition
-     */
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
     @Postsubmit
     @Test
     fun navBarLayerPositionAtEnd() {
@@ -134,40 +121,31 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() { }
+    override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /**
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
-    @Postsubmit
-    @Test
-    fun statusBarLayerPositionEnd() = testSpec.statusBarLayerPositionAtEnd()
+    @Postsubmit @Test fun statusBarLayerPositionEnd() = testSpec.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()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    fun navBarLayerIsVisibleAtEnd() = testSpec.navBarLayerIsVisibleAtEnd()
+    @Postsubmit @Test fun navBarLayerIsVisibleAtEnd() = testSpec.navBarLayerIsVisibleAtEnd()
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
-    @Postsubmit
-    @Test
-    fun navBarWindowIsVisibleAtEnd() = testSpec.navBarWindowIsVisibleAtEnd()
+    @Postsubmit @Test fun navBarWindowIsVisibleAtEnd() = testSpec.navBarWindowIsVisibleAtEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -175,14 +153,10 @@
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -197,10 +171,7 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowIsTopWindowAtEnd() =
-        super.appWindowIsTopWindowAtEnd()
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -212,14 +183,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 83350ea..0b4361c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
@@ -34,8 +33,8 @@
 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.
+ * 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`
  */
@@ -43,12 +42,11 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 @Postsubmit
 class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) :
     OpenAppFromLockNotificationCold(testSpec) {
     private val showWhenLockedApp: ShowWhenLockedAppHelper =
-            ShowWhenLockedAppHelper(instrumentation)
+        ShowWhenLockedAppHelper(instrumentation)
 
     // Although we are technically still locked here, the overlay app means we should open the
     // notification shade as if we were unlocked.
@@ -63,19 +61,13 @@
 
                 // Launch an activity that is shown when the device is locked
                 showWhenLockedApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder()
-                    .withFullScreenApp(showWhenLockedApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
 
                 device.sleep()
-                wmHelper.StateSyncBuilder()
-                    .withoutTopVisibleAppWindows()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
             }
 
-            teardown {
-                showWhenLockedApp.exit(wmHelper)
-            }
+            teardown { showWhenLockedApp.exit(wmHelper) }
         }
 
     @Test
@@ -83,10 +75,10 @@
     fun showWhenLockedAppWindowBecomesVisible() {
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowOnTop(showWhenLockedApp)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(showWhenLockedApp)
         }
     }
 
@@ -95,10 +87,10 @@
     fun showWhenLockedAppLayerBecomesVisible() {
         testSpec.assertLayers {
             this.isInvisible(showWhenLockedApp)
-                    .then()
-                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isVisible(showWhenLockedApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(showWhenLockedApp)
         }
     }
 
@@ -108,22 +100,19 @@
     override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index f574c9e..3cc2390 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -27,40 +27,26 @@
 import org.junit.Ignore
 import org.junit.Test
 
-/**
- * Base class for app launch tests from lock screen
- */
+/** Base class for app launch tests from lock screen */
 abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
     OpenAppTransition(testSpec) {
 
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit = {
         super.transition(this)
         setup {
             device.sleep()
-            wmHelper.StateSyncBuilder()
-                .withoutTopVisibleAppWindows()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
-        transitions {
-            testApp.launchViaIntent(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.launchViaIntent(wmHelper) }
     }
 
-    /**
-     * Check that we go from no focus to focus on the [testApp]
-     */
+    /** Check that we go from no focus to focus on the [testApp] */
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog {
-            this.focusChanges("", testApp.`package`)
-        }
+        testSpec.assertEventLog { this.focusChanges("", testApp.`package`) }
     }
 
     /**
@@ -72,24 +58,20 @@
     open fun appWindowBecomesFirstAndOnlyTopWindow() {
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                    .then()
-                    .isAppWindowOnTop(testApp)
+                .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
-     */
+    /** Checks that the screen is locked at the start of the transition */
     @Presubmit
     @Test
     fun screenLockedStart() {
-        testSpec.assertLayersStart {
-            isEmpty()
-        }
+        testSpec.assertLayersStart { isEmpty() }
     }
 
     /** {@inheritDoc} */
@@ -100,26 +82,24 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() { }
+    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() { }
+    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() { }
+    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() { }
+    override fun taskBarWindowIsAlwaysVisible() {}
 
-    /**
-     * Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition
-     */
+    /** Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition */
     @Presubmit
     @Test
     open fun navBarLayerPositionAtEnd() {
@@ -127,17 +107,13 @@
         testSpec.navBarLayerPositionAtEnd()
     }
 
-    /**
-     * Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+    /** Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() { }
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
 
     /**
      * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the end of the trace
@@ -147,8 +123,6 @@
     @Presubmit
     @Test
     fun statusBarLayerIsVisibleAtEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     }
 }
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
index 8d349a6..6802d7a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -21,7 +21,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
@@ -39,11 +38,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 @Postsubmit
-open class OpenAppFromNotificationCold(
-    testSpec: FlickerTestParameter
-) : OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
+    OpenAppFromNotificationWarm(testSpec) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -62,14 +59,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 77f28f6..1ae0d53 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NotificationAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
@@ -54,11 +53,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
 @Postsubmit
-open class OpenAppFromNotificationWarm(
-    testSpec: FlickerTestParameter
-) : OpenAppTransition(testSpec) {
+open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
+    OpenAppTransition(testSpec) {
     override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
 
     open val openingNotificationsFromLockScreen = false
@@ -70,14 +67,10 @@
                 device.wakeUpAndGoToHomeScreen()
                 this.setRotation(testSpec.startRotation)
                 testApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder()
-                    .withFullScreenApp(testApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
                 testApp.postNotification(wmHelper)
-                tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                device.pressHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
 
             transitions {
@@ -89,10 +82,10 @@
                         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()
-                    )
+                    val insets =
+                        metricInsets.getInsetsIgnoringVisibility(
+                            WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                        )
 
                     startY = insets.top + 100
                     endY = device.displayHeight / 2
@@ -106,23 +99,16 @@
                 instrumentation.uiAutomation.syncInputTransactions()
 
                 // Launch the activity by clicking the notification
-                val notification = device.wait(
-                    Until.findObject(
-                        By.text("Flicker Test Notification")
-                    ), 2000L
-                )
+                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()
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
             }
 
-            teardown {
-                testApp.exit(wmHelper)
-            }
+            teardown { testApp.exit(wmHelper) }
         }
 
     /** {@inheritDoc} */
@@ -139,8 +125,7 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -149,14 +134,10 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+    @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+    @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -164,9 +145,7 @@
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -185,33 +164,24 @@
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun appWindowIsTopWindowAtEnd() =
-        super.appWindowIsTopWindowAtEnd()
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
 
     @Postsubmit
     @Test
     open fun notificationAppWindowVisibleAtEnd() {
-        testSpec.assertWmEnd {
-            this.isAppWindowVisible(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
     }
 
     @Postsubmit
     @Test
     open fun notificationAppWindowOnTopAtEnd() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 
     @Postsubmit
     @Test
     open fun notificationAppLayerVisibleAtEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(testApp)
-        }
+        testSpec.assertLayersEnd { this.isVisible(testApp) }
     }
 
     /** {@inheritDoc} */
@@ -243,8 +213,7 @@
     }
 
     /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the
-     * transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition
      *
      * Note: Large screen only
      */
@@ -258,27 +227,24 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Display is locked at the start")
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Display is locked at the start")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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 bc86cdf..fd8a38c 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
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -39,32 +38,31 @@
  * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
  *
  * Actions:
+ * ```
  *     Launch [testApp]
  *     Press recents
  *     Relaunch an app [testApp] by selecting it in the overview screen, 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)
-@Group1
-open class OpenAppFromOverviewTest(
-    testSpec: FlickerTestParameter
-) : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
+    OpenAppFromLauncherTransition(testSpec) {
 
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -72,9 +70,7 @@
                 tapl.setExpectedRotationCheckEnabled(false)
                 testApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
                 // By default, launcher doesn't rotate on phones, but rotates on tablets
                 if (testSpec.isTablet) {
                     tapl.setExpectedRotation(testSpec.startRotation)
@@ -82,23 +78,17 @@
                     tapl.setExpectedRotation(Surface.ROTATION_0)
                 }
                 tapl.workspace.switchToOverview()
-                wmHelper.StateSyncBuilder()
-                    .withRecentsActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
                 this.setRotation(testSpec.startRotation)
             }
             transitions {
                 tapl.overview.currentTask.open()
-                wmHelper.StateSyncBuilder()
-                    .withFullScreenApp(testApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
             }
         }
 
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
     @FlakyTest
@@ -119,14 +109,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
index 82e30ac..89e3b5f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -46,23 +45,25 @@
  * 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)
-@Group1
 open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
     OpenAppFromLockTransition(testSpec) {
     override val testApp = NonResizeableAppHelper(instrumentation)
@@ -82,15 +83,11 @@
         }
     }
 
-    /**
-     * Checks if [testApp] is visible at the end of the transition
-     */
+    /** Checks if [testApp] is visible at the end of the transition */
     @Presubmit
     @Test
     fun appWindowBecomesVisibleAtEnd() {
-        testSpec.assertWmEnd {
-            this.isAppWindowVisible(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
     }
 
     /**
@@ -116,9 +113,7 @@
     @Test
     fun taskBarLayerIsVisibleAtEnd() {
         Assume.assumeTrue(testSpec.isTablet)
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.TASK_BAR)
-        }
+        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
     }
 
     /**
@@ -129,59 +124,43 @@
     @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.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() {
-    }
+    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() {
-    }
+    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() {
-    }
+    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() {
-    }
+    override fun navBarWindowIsAlwaysVisible() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarWindowIsAlwaysVisible() {
-    }
+    override fun statusBarWindowIsAlwaysVisible() {}
 
-    /**
-     * Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerPositionEnd() = testSpec.statusBarLayerPositionAtEnd()
+    /** Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition */
+    @Presubmit @Test fun statusBarLayerPositionEnd() = testSpec.statusBarLayerPositionAtEnd()
 
-    /**
-     * Checks the [ComponentMatcher.NAV_BAR] is visible at the end of the transition
-     */
+    /** Checks the [ComponentMatcher.NAV_BAR] is visible at the end of the transition */
     @Postsubmit
     @Test
     fun navBarLayerIsVisibleAtEnd() {
         Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertLayersEnd {
-            this.isVisible(ComponentNameMatcher.NAV_BAR)
-        }
+        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
     }
 
     /** {@inheritDoc} */
@@ -207,9 +186,7 @@
     }
 
     /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     @FlakyTest(bugId = 218470989)
     @Test
@@ -218,23 +195,28 @@
 
     @FlakyTest(bugId = 227143265)
     @Test
-    override fun appWindowBecomesTopWindow() =
-        super.appWindowBecomesTopWindow()
+    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+    @FlakyTest(bugId = 251217585)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
                     supportedRotations = listOf(Surface.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 face7da..4fd251a 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
@@ -27,9 +27,7 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
-/**
- * Base class for app launch tests
- */
+/** Base class for app launch tests */
 abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
@@ -40,14 +38,12 @@
             device.wakeUpAndGoToHomeScreen()
             this.setRotation(testSpec.startRotation)
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        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.
+     * 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
@@ -85,12 +81,10 @@
      * 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.
+     * 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()
+    @Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
 
     protected fun appWindowBecomesVisible_coldStart() {
         testSpec.assertWm {
@@ -125,9 +119,7 @@
             this.isAppWindowNotOnTop(testApp)
                 .then()
                 .isAppWindowOnTop(
-                    testApp
-                        .or(ComponentNameMatcher.SNAPSHOT)
-                        .or(ComponentNameMatcher.SPLASH_SCREEN)
+                    testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
                 )
         }
     }
@@ -139,8 +131,6 @@
     @Presubmit
     @Test
     open fun appWindowIsTopWindowAtEnd() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 }
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
index 8077398..03741c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -38,29 +37,29 @@
  * 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)
-@Group1
 open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
     OpenAppFromLauncherTransition(testSpec) {
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -68,17 +67,11 @@
                 tapl.setExpectedRotationCheckEnabled(false)
                 testApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
                 this.setRotation(testSpec.startRotation)
             }
-            teardown {
-                testApp.exit(wmHelper)
-            }
-            transitions {
-                testApp.launchViaIntent(wmHelper)
-            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
         }
 
     /** {@inheritDoc} */
@@ -87,9 +80,7 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -105,14 +96,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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
new file mode 100644
index 0000000..bc2fe46
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.Instrumentation
+import android.os.Bundle
+import android.os.Handler
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.R
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import org.junit.Assume
+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 [android.app.ActivityOptions.makeCustomTaskAnimation].
+ *
+ * To run this test: `atest FlickerTests:OverrideTaskTransitionTest`
+ *
+ * Actions:
+ * ```
+ *     Launches SimpleActivity with alpha_2000ms animation
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+                setRotation(testSpec.startRotation)
+            }
+            transitions {
+                instrumentation.context.startActivity(
+                    testApp.openAppIntent,
+                    createCustomTaskAnimation()
+                )
+                wmHelper
+                    .StateSyncBuilder()
+                    .add(WindowManagerConditionsFactory.isWMStateComplete())
+                    .withAppTransitionIdle()
+                    .withWindowSurfaceAppeared(testApp)
+                    .waitForAndVerify()
+            }
+            teardown { testApp.exit() }
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun testSimpleActivityIsShownDirectly() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.assertLayers {
+            isVisible(ComponentNameMatcher.LAUNCHER)
+                .isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+                .isInvisible(testApp)
+                .then()
+                // The custom animation should block the entire launcher from the very beginning
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    private fun createCustomTaskAnimation(): Bundle {
+        return android.app.ActivityOptions.makeCustomTaskAnimation(
+                instrumentation.context,
+                R.anim.show_2000ms,
+                0,
+                Handler.getMain(),
+                null,
+                null
+            )
+            .toBundle()
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        }
+    }
+}
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 26f46cd..06486ca 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
@@ -24,12 +24,10 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
-import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -47,15 +45,16 @@
  * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
  *
  * Actions:
+ * ```
  *     Launch the NewTaskLauncherApp [mTestApp]
  *     Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp]
  *     Go back to the NewTaskLauncherApp [mTestApp]
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
 class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
     private val wallpaper by lazy {
@@ -64,36 +63,28 @@
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            testApp.launchViaIntent(wmHelper)
-        }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        setup { testApp.launchViaIntent(wmHelper) }
+        teardown { testApp.exit(wmHelper) }
         transitions {
             testApp.openNewTask(device, wmHelper)
             tapl.pressBack()
-            wmHelper.StateSyncBuilder()
-                .withAppTransitionIdle()
-                .waitForAndVerify()
+            wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
         }
     }
 
     /**
-     * Checks that the [wallpaper] window is never visible when performing task transitions.
-     * A solid color background should be shown instead.
+     * Checks that the [wallpaper] window is never visible when performing task transitions. A solid
+     * color background should be shown instead.
      */
     @Postsubmit
     @Test
     fun wallpaperWindowIsNeverVisible() {
-        testSpec.assertWm {
-            this.isNonAppWindowInvisible(wallpaper)
-        }
+        testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
     }
 
     /**
-     * Checks that the [wallpaper] layer is never visible when performing task transitions.
-     * A solid color background should be shown instead.
+     * Checks that the [wallpaper] layer is never visible when performing task transitions. A solid
+     * color background should be shown instead.
      */
     @Postsubmit
     @Test
@@ -105,34 +96,26 @@
     }
 
     /**
-     * Check that the [ComponentMatcher.LAUNCHER] window is never visible when performing task
-     * transitions.
-     * A solid color background should be shown above it.
+     * Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task
+     * transitions. A solid color background should be shown above it.
      */
     @Postsubmit
     @Test
     fun launcherWindowIsNeverVisible() {
-        testSpec.assertWm {
-            this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
-        }
+        testSpec.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible when performing task
-     * transitions.
-     * A solid color background should be shown above it.
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task
+     * transitions. A solid color background should be shown above it.
      */
     @Postsubmit
     @Test
     fun launcherLayerIsNeverVisible() {
-        testSpec.assertLayers {
-            this.isInvisible(ComponentNameMatcher.LAUNCHER)
-        }
+        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
-    /**
-     * Checks that a color background is visible while the task transition is occurring.
-     */
+    /** Checks that a color background is visible while the task transition is occurring. */
     @Postsubmit
     @Test
     fun colorLayerIsVisibleDuringTransition() {
@@ -141,8 +124,8 @@
 
         testSpec.assertLayers {
             this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
-                it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
-            }
+                    it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+                }
                 .isInvisible(bgColorLayer)
                 .then()
                 // Transitioning
@@ -166,8 +149,8 @@
     }
 
     /**
-     * Checks that we start with the LaunchNewTask activity on top and then open up
-     * the SimpleActivity and then go back to the LaunchNewTask activity.
+     * Checks that we start with the LaunchNewTask activity on top and then open up the
+     * SimpleActivity and then go back to the LaunchNewTask activity.
      */
     @Postsubmit
     @Test
@@ -186,9 +169,7 @@
     }
 
     /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -209,8 +190,7 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -246,8 +226,8 @@
 
     companion object {
         private val LAUNCH_NEW_TASK_ACTIVITY =
-            LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
-        private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
 
         private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
             val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
@@ -258,8 +238,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
         }
     }
 }
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 a1df1df..46186bc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -24,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -45,19 +45,17 @@
  * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
  *
  * Actions:
+ * ```
  *     Launch an app [testApp1]
  *     Launch another app [testApp2]
  *     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)
-@Group1
-open class QuickSwitchBetweenTwoAppsBackTest(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -70,14 +68,16 @@
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
             tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setIgnoreTaskbarVisibility(true)
             testApp1.launchViaIntent(wmHelper)
             testApp2.launchViaIntent(wmHelper)
-            startDisplayBounds = wmHelper.currentState.layerState
-                .physicalDisplayBounds ?: error("Display not found")
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
         }
         transitions {
             tapl.launchedAppState.quickSwitchToPreviousApp()
-            wmHelper.StateSyncBuilder()
+            wmHelper
+                .StateSyncBuilder()
                 .withFullScreenApp(testApp1)
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
@@ -97,16 +97,14 @@
     @Presubmit
     @Test
     open fun startsWithApp2WindowsCoverFullScreen() {
-        testSpec.assertWmStart {
-            this.visibleRegion(testApp2).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
     }
 
     /**
      * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @Presubmit
+    @FlakyTest(bugId = 250520840)
     @Test
     open fun startsWithApp2LayersCoverFullScreen() {
         testSpec.assertLayersStart {
@@ -114,15 +112,11 @@
         }
     }
 
-    /**
-     * Checks that the transition starts with [testApp2] being the top window.
-     */
+    /** Checks that the transition starts with [testApp2] being the top window. */
     @Presubmit
     @Test
     open fun startsWithApp2WindowBeingOnTop() {
-        testSpec.assertWmStart {
-            this.isAppWindowOnTop(testApp2)
-        }
+        testSpec.assertWmStart { this.isAppWindowOnTop(testApp2) }
     }
 
     /**
@@ -132,21 +126,17 @@
     @Presubmit
     @Test
     open fun endsWithApp1WindowsCoveringFullScreen() {
-        testSpec.assertWmEnd {
-            this.visibleRegion(testApp1).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
     }
 
     /**
-     * Checks that [testApp1] layers fill the entire screen (i.e. is "fullscreen") at the end of
-     * the transition once we have fully quick switched from [testApp2] back to the [testApp1].
+     * Checks that [testApp1] layers fill the entire screen (i.e. is "fullscreen") at the end of the
+     * transition once we have fully quick switched from [testApp2] back to the [testApp1].
      */
     @Presubmit
     @Test
     fun endsWithApp1LayersCoveringFullScreen() {
-        testSpec.assertLayersEnd {
-            this.visibleRegion(testApp1).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -156,14 +146,12 @@
     @Presubmit
     @Test
     open fun endsWithApp1BeingOnTop() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp1)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp1) }
     }
 
     /**
-     * Checks that [testApp1]'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.
+     * Checks that [testApp1]'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.
      */
     @Presubmit
     @Test
@@ -178,45 +166,35 @@
     }
 
     /**
-     * Checks that [testApp1]'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.
+     * Checks that [testApp1]'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.
      */
     @Presubmit
     @Test
     open fun app1LayerBecomesAndStaysVisible() {
-        testSpec.assertLayers {
-            this.isInvisible(testApp1)
-                .then()
-                .isVisible(testApp1)
-        }
+        testSpec.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
     }
 
     /**
-     * Checks that [testApp2]'s 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.
+     * Checks that [testApp2]'s 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.
      */
     @Presubmit
     @Test
     open fun app2WindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
-            this.isAppWindowVisible(testApp2)
-                .then()
-                .isAppWindowInvisible(testApp2)
+            this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2)
         }
     }
 
     /**
-     * Checks that [testApp2]'s 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.
+     * Checks that [testApp2]'s 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.
      */
     @Presubmit
     @Test
     open fun app2LayerBecomesAndStaysInvisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp2)
-                .then()
-                .isInvisible(testApp2)
-        }
+        testSpec.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
     }
 
     /**
@@ -263,6 +241,10 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
@@ -271,9 +253,8 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                                        supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    ),
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
                     supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
                 )
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 49dcbcf..7a1350e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
@@ -38,19 +38,18 @@
  * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
  *
  * Actions:
+ * ```
  *     Launch an app [testApp1]
  *     Launch another app [testApp2]
  *     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)
-@Group1
-open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(
-    testSpec: FlickerTestParameter
-) : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) :
+    QuickSwitchBetweenTwoAppsBackTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
@@ -62,8 +61,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
-     * and end of the WM trace
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
@@ -71,4 +70,9 @@
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.navBarWindowIsVisibleAtStartAndEnd()
     }
+
+    @FlakyTest(bugId = 246284708)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
 }
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 5ab9f14..0a21044 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -24,7 +25,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -45,19 +45,19 @@
  * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
  *
  * Actions:
+ * ```
  *     Launch an app [testApp1]
  *     Launch another app [testApp2]
  *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
  *     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)
-@Group1
-open class QuickSwitchBetweenTwoAppsForwardTest(
-    testSpec: FlickerTestParameter
-) : BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter) :
+    BaseTest(testSpec) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -69,22 +69,24 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-                tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(testSpec.startRotation)
 
-                testApp1.launchViaIntent(wmHelper)
-                testApp2.launchViaIntent(wmHelper)
-                tapl.launchedAppState.quickSwitchToPreviousApp()
-                wmHelper.StateSyncBuilder()
-                    .withFullScreenApp(testApp1)
-                    .withNavOrTaskBarVisible()
-                    .withStatusBarVisible()
-                    .waitForAndVerify()
-                startDisplayBounds = wmHelper.currentState.layerState
-                    .physicalDisplayBounds ?: error("Display not found")
+            testApp1.launchViaIntent(wmHelper)
+            testApp2.launchViaIntent(wmHelper)
+            tapl.launchedAppState.quickSwitchToPreviousApp()
+            wmHelper
+                .StateSyncBuilder()
+                .withFullScreenApp(testApp1)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
         }
         transitions {
             tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
-            wmHelper.StateSyncBuilder()
+            wmHelper
+                .StateSyncBuilder()
                 .withFullScreenApp(testApp2)
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
@@ -114,7 +116,7 @@
      * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @Presubmit
+    @FlakyTest(bugId = 250522691)
     @Test
     open fun startsWithApp1LayersCoverFullScreen() {
         testSpec.assertLayersStart {
@@ -122,15 +124,11 @@
         }
     }
 
-    /**
-     * Checks that the transition starts with [testApp1] being the top window.
-     */
+    /** Checks that the transition starts with [testApp1] being the top window. */
     @Presubmit
     @Test
     open fun startsWithApp1WindowBeingOnTop() {
-        testSpec.assertWmStart {
-            this.isAppWindowOnTop(testApp1)
-        }
+        testSpec.assertWmStart { this.isAppWindowOnTop(testApp1) }
     }
 
     /**
@@ -140,9 +138,7 @@
     @Presubmit
     @Test
     open fun endsWithApp2WindowsCoveringFullScreen() {
-        testSpec.assertWmEnd {
-            this.visibleRegion(testApp2).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -165,9 +161,7 @@
     @Presubmit
     @Test
     open fun endsWithApp2BeingOnTop() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp2)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp2) }
     }
 
     /**
@@ -179,10 +173,10 @@
     open fun app2WindowBecomesAndStaysVisible() {
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp2)
-                    .then()
-                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowVisible(testApp2)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp2)
         }
     }
 
@@ -193,11 +187,7 @@
     @Presubmit
     @Test
     open fun app2LayerBecomesAndStaysVisible() {
-        testSpec.assertLayers {
-            this.isInvisible(testApp2)
-                    .then()
-                    .isVisible(testApp2)
-        }
+        testSpec.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
     }
 
     /**
@@ -208,9 +198,7 @@
     @Test
     open fun app1WindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
-            this.isAppWindowVisible(testApp1)
-                    .then()
-                    .isAppWindowInvisible(testApp1)
+            this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1)
         }
     }
 
@@ -221,11 +209,7 @@
     @Presubmit
     @Test
     open fun app1LayerBecomesAndStaysInvisible() {
-        testSpec.assertLayers {
-            this.isVisible(testApp1)
-                    .then()
-                    .isInvisible(testApp1)
-        }
+        testSpec.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
     }
 
     /**
@@ -238,12 +222,12 @@
     open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1)
-                    .then()
-                    .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
-                    .then()
-                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowVisible(testApp2)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp2)
         }
     }
 
@@ -257,12 +241,12 @@
     open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
         testSpec.assertLayers {
             this.isVisible(testApp1)
-                    .then()
-                    .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
-                    .then()
-                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isVisible(testApp2)
+                .then()
+                .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(testApp2)
         }
     }
 
@@ -271,6 +255,10 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
@@ -278,12 +266,11 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(
-                                                        supportedNavigationModes = listOf(
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                            ),
-                            supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
-                    )
+                .getConfigNonRotationTests(
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
+                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
+                )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 7c7be89..03647c9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
@@ -38,19 +38,19 @@
  * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
  *
  * Actions:
+ * ```
  *     Launch an app [testApp1]
  *     Launch another app [testApp2]
  *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
  *     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)
-@Group1
-open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(
-    testSpec: FlickerTestParameter
-) : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) :
+    QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
@@ -62,8 +62,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
-     * and end of the WM trace
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
@@ -71,4 +71,9 @@
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.navBarWindowIsVisibleAtStartAndEnd()
     }
+
+    @FlakyTest(bugId = 246284708)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
 }
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 00e6023..3cb985a 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
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -24,9 +25,9 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
@@ -44,16 +45,16 @@
  * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest`
  *
  * Actions:
+ * ```
  *     Launch an 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)
-@Group1
 class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     private val testApp = SimpleAppHelper(instrumentation)
 
@@ -66,25 +67,25 @@
 
             testApp.launchViaIntent(wmHelper)
             tapl.goHome()
-            wmHelper.StateSyncBuilder()
+            wmHelper
+                .StateSyncBuilder()
                 .withHomeActivityVisible()
                 .withWindowSurfaceDisappeared(testApp)
                 .waitForAndVerify()
 
-            startDisplayBounds = wmHelper.currentState.layerState
-                .physicalDisplayBounds ?: error("Display not found")
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
         }
         transitions {
             tapl.workspace.quickSwitchToPreviousApp()
-            wmHelper.StateSyncBuilder()
+            wmHelper
+                .StateSyncBuilder()
                 .withFullScreenApp(testApp)
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
                 .waitForAndVerify()
         }
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
     }
 
     /**
@@ -94,9 +95,7 @@
     @Presubmit
     @Test
     fun endsWithAppWindowsCoveringFullScreen() {
-        testSpec.assertWmEnd {
-            this.visibleRegion(testApp).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -106,9 +105,7 @@
     @Presubmit
     @Test
     fun endsWithAppLayersCoveringFullScreen() {
-        testSpec.assertLayersEnd {
-            this.visibleRegion(testApp).coversExactly(startDisplayBounds)
-        }
+        testSpec.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -118,20 +115,14 @@
     @Presubmit
     @Test
     fun endsWithAppBeingOnTop() {
-        testSpec.assertWmEnd {
-            this.isAppWindowOnTop(testApp)
-        }
+        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 
-    /**
-     * Checks that the transition starts with the home activity being tagged as visible.
-     */
+    /** Checks that the transition starts with the home activity being tagged as visible. */
     @Presubmit
     @Test
     fun startsWithHomeActivityFlaggedVisible() {
-        testSpec.assertWmStart {
-            this.isHomeActivityVisible()
-        }
+        testSpec.assertWmStart { this.isHomeActivityVisible() }
     }
 
     /**
@@ -164,9 +155,7 @@
     @Presubmit
     @Test
     fun startsWithLauncherBeingOnTop() {
-        testSpec.assertWmStart {
-            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-        }
+        testSpec.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
@@ -176,9 +165,7 @@
     @Presubmit
     @Test
     fun endsWithHomeActivityFlaggedInvisible() {
-        testSpec.assertWmEnd {
-            this.isHomeActivityInvisible()
-        }
+        testSpec.assertWmEnd { this.isHomeActivityInvisible() }
     }
 
     /**
@@ -188,11 +175,7 @@
     @Presubmit
     @Test
     fun appWindowBecomesAndStaysVisible() {
-        testSpec.assertWm {
-            this.isAppWindowInvisible(testApp)
-                    .then()
-                    .isAppWindowVisible(testApp)
-        }
+        testSpec.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
     }
 
     /**
@@ -202,63 +185,59 @@
     @Presubmit
     @Test
     fun appLayerBecomesAndStaysVisible() {
-        testSpec.assertLayers {
-            this.isInvisible(testApp)
-                    .then()
-                    .isVisible(testApp)
-        }
+        testSpec.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
     }
 
     /**
      * Checks that the [ComponentMatcher.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.
+     * at some point before the end of the transition and then stays invisible until the end of the
+     * transition.
      */
     @Presubmit
     @Test
     fun launcherWindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
     /**
-     * Checks that the [ComponentMatcher.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.
+     * Checks that the [ComponentMatcher.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.
      */
     @Presubmit
     @Test
     fun launcherLayerBecomesAndStaysInvisible() {
         testSpec.assertLayers {
             this.isVisible(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isInvisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
     /**
      * Checks that the [ComponentMatcher.LAUNCHER] window is visible at least until the app window
-     * is visible. Ensures
-     * that at any point, either the launcher or [testApp] windows are at least partially visible.
+     * is visible. Ensures that at any point, either the launcher or [testApp] windows are at least
+     * partially visible.
      */
     @Presubmit
     @Test
     fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
         testSpec.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isAppWindowVisible(testApp)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp)
         }
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is visible at least until the app layer
-     * is visible. Ensures that at any point, either the launcher or [testApp] layers are at least
+     * Checks that the [ComponentMatcher.LAUNCHER] layer is visible at least until the app layer is
+     * visible. Ensures that at any point, either the launcher or [testApp] layers are at least
      * partially visible.
      */
     @Presubmit
@@ -266,10 +245,10 @@
     fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
         testSpec.assertLayers {
             this.isVisible(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then()
-                    .isVisible(testApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(testApp)
         }
     }
 
@@ -284,8 +263,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
-     * and end of the WM trace
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
@@ -294,6 +273,20 @@
         testSpec.navBarWindowIsVisibleAtStartAndEnd()
     }
 
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 246285528)
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
@@ -302,13 +295,12 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(
-                                                        supportedNavigationModes = listOf(
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                            ),
-                            // TODO: Test with 90 rotation
-                            supportedRotations = listOf(Surface.ROTATION_0)
-                    )
+                .getConfigNonRotationTests(
+                    supportedNavigationModes =
+                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
+                    // TODO: Test with 90 rotation
+                    supportedRotations = listOf(Surface.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 f24f71e..1973ec0 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
@@ -22,7 +22,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -36,52 +35,54 @@
  * Test opening an app and cycling through app rotations
  *
  * Currently runs:
+ * ```
  *      0 -> 90 degrees
  *      90 -> 0 degrees
- *
+ * ```
  * Actions:
+ * ```
  *     Launch an app (via intent)
  *     Set initial device orientation
  *     Start tracing
  *     Change device orientation
  *     Stop tracing
- *
+ * ```
  * To run this test: `atest FlickerTests:ChangeAppRotationTest`
  *
  * To run only the presubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
+ * ```
  * To run only the postsubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
+ * ```
  * To run only the flaky assertions add: `--
+ * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [RotationTransition]
  *     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)
-@Group3
-class ChangeAppRotationTest(
-    testSpec: FlickerTestParameter
-) : RotationTransition(testSpec) {
+class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
     override val testApp = SimpleAppHelper(instrumentation)
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                testApp.launchViaIntent(wmHelper)
-            }
+            setup { testApp.launchViaIntent(wmHelper) }
         }
 
     /**
@@ -91,14 +92,12 @@
     @Presubmit
     @Test
     fun focusChanges() {
-        testSpec.assertEventLog {
-            this.focusChanges(testApp.`package`)
-        }
+        testSpec.assertEventLog { this.focusChanges(testApp.`package`) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition,
-     * doesn't flicker, and disappears before the transition is complete
+     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+     * flicker, and disappears before the transition is complete
      */
     fun rotationLayerAppearsAndVanishesAssertion() {
         testSpec.assertLayers {
@@ -112,8 +111,8 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition,
-     * doesn't flicker, and disappears before the transition is complete
+     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+     * flicker, and disappears before the transition is complete
      */
     @Presubmit
     @Test
@@ -124,21 +123,19 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests()
+            return FlickerTestParameterFactory.getInstance().getConfigRotationTests()
         }
     }
 }
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 afe2ea6..4faeb24 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
@@ -25,23 +25,15 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
-/**
- * Base class for app rotation tests
- */
+/** Base class for app rotation tests */
 abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected abstract val testApp: StandardAppHelper
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            this.setRotation(testSpec.startRotation)
-        }
-        teardown {
-            testApp.exit(wmHelper)
-        }
-        transitions {
-            this.setRotation(testSpec.endRotation)
-        }
+        setup { this.setRotation(testSpec.startRotation) }
+        teardown { testApp.exit(wmHelper) }
+        transitions { this.setRotation(testSpec.endRotation) }
     }
 
     /** {@inheritDoc} */
@@ -50,18 +42,17 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
-                ignoreLayers = listOf(
-                    ComponentNameMatcher.SPLASH_SCREEN,
-                    ComponentNameMatcher.SNAPSHOT,
-                    ComponentNameMatcher("", "SecondaryHomeHandle")
-                )
+                ignoreLayers =
+                    listOf(
+                        ComponentNameMatcher.SPLASH_SCREEN,
+                        ComponentNameMatcher.SNAPSHOT,
+                        ComponentNameMatcher("", "SecondaryHomeHandle")
+                    )
             )
         }
     }
 
-    /**
-     * Checks that [testApp] layer covers the entire screen at the start of the transition
-     */
+    /** Checks that [testApp] layer covers the entire screen at the start of the transition */
     @Presubmit
     @Test
     open fun appLayerRotates_StartingPos() {
@@ -72,9 +63,7 @@
         }
     }
 
-    /**
-     * Checks that [testApp] layer covers the entire screen at the end of the transition
-     */
+    /** Checks that [testApp] layer covers the entire screen at the end of the transition */
     @Presubmit
     @Test
     open fun appLayerRotates_EndingPos() {
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 16ad630..a08db29 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
@@ -23,7 +23,6 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -39,47 +38,51 @@
  * Test opening an app and cycling through app rotations using seamless rotations
  *
  * Currently runs:
+ * ```
  *      0 -> 90 degrees
  *      0 -> 90 degrees (with starved UI thread)
  *      90 -> 0 degrees
  *      90 -> 0 degrees (with starved UI thread)
- *
+ * ```
  * Actions:
+ * ```
  *     Launch an app in fullscreen and supporting seamless rotation (via intent)
  *     Set initial device orientation
  *     Start tracing
  *     Change device orientation
  *     Stop tracing
- *
+ * ```
  * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
  *
  * To run only the presubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
+ * ```
  * To run only the postsubmit assertions add: `--
+ * ```
  *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
  *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
+ * ```
  * To run only the flaky assertions add: `--
+ * ```
  *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [RotationTransition]
  *     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)
-@Group3
-open class SeamlessAppRotationTest(
-    testSpec: FlickerTestParameter
-) : RotationTransition(testSpec) {
+open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -89,17 +92,16 @@
             setup {
                 testApp.launchViaIntent(
                     wmHelper,
-                    stringExtras = mapOf(
-                        ActivityOptions.EXTRA_STARVE_UI_THREAD
-                            to testSpec.starveUiThread.toString()
-                    )
+                    stringExtras =
+                        mapOf(
+                            ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to
+                                testSpec.starveUiThread.toString()
+                        )
                 )
             }
         }
 
-    /**
-     * Checks that [testApp] window is always in full screen
-     */
+    /** Checks that [testApp] window is always in full screen */
     @Presubmit
     @Test
     fun appWindowFullScreen() {
@@ -107,16 +109,15 @@
             this.invoke("isFullScreen") {
                 val appWindow = it.windowState(testApp.`package`)
                 val flags = appWindow.windowState?.attributes?.flags ?: 0
-                appWindow.verify("isFullScreen")
+                appWindow
+                    .verify("isFullScreen")
                     .that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN))
                     .isGreaterThan(0)
             }
         }
     }
 
-    /**
-     * Checks that [testApp] window is always with seamless rotation
-     */
+    /** Checks that [testApp] window is always with seamless rotation */
     @Presubmit
     @Test
     fun appWindowSeamlessRotation() {
@@ -124,38 +125,33 @@
             this.invoke("isRotationSeamless") {
                 val appWindow = it.windowState(testApp.`package`)
                 val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
-                appWindow.verify("isRotationSeamless")
+                appWindow
+                    .verify("isRotationSeamless")
                     .that(
-                        rotationAnimation
-                            .and(WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS)
+                        rotationAnimation.and(
+                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                        )
                     )
                     .isGreaterThan(0)
             }
         }
     }
 
-    /**
-     * Checks that [testApp] window is always visible
-     */
+    /** Checks that [testApp] window is always visible */
     @Presubmit
     @Test
     fun appLayerAlwaysVisible() {
-        testSpec.assertLayers {
-            isVisible(testApp)
-        }
+        testSpec.assertLayers { isVisible(testApp) }
     }
 
-    /**
-     * Checks that [testApp] layer covers the entire screen during the whole transition
-     */
+    /** Checks that [testApp] layer covers the entire screen during the whole transition */
     @Presubmit
     @Test
     fun appLayerRotates() {
         testSpec.assertLayers {
             this.invoke("entireScreenCovered") { entry ->
                 entry.entry.displays.map { display ->
-                    entry.visibleRegion(testApp)
-                        .coversExactly(display.layerStackSpace)
+                    entry.visibleRegion(testApp).coversExactly(display.layerStackSpace)
                 }
             }
         }
@@ -164,51 +160,43 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarLayerPositionAtStartAndEnd() { }
+    override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() { }
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarWindowIsAlwaysVisible() { }
+    override fun statusBarWindowIsAlwaysVisible() {}
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] window is invisible during the whole
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is invisible during the whole
      * transition
      */
     @Presubmit
     @Test
     fun statusBarWindowIsAlwaysInvisible() {
-        testSpec.assertWm {
-            this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is invisible during the whole
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is invisible during the whole
      * transition
      */
     @Presubmit
     @Test
     fun statusBarLayerIsAlwaysInvisible() {
-        testSpec.assertLayers {
-            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
-        }
+        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
-    /**
-     * Checks that the focus doesn't change during animation
-     */
+    /** Checks that the focus doesn't change during animation */
     @Presubmit
     @Test
     fun focusDoesNotChange() {
-        testSpec.assertEventLog {
-            this.focusDoesNotChange()
-        }
+        testSpec.assertEventLog { this.focusDoesNotChange() }
     }
 
     /** {@inheritDoc} */
@@ -218,40 +206,43 @@
 
     companion object {
         private val FlickerTestParameter.starveUiThread
-            get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
+            get() =
+                config.getOrDefault(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false)
+                    as Boolean
 
         private fun createConfig(
             sourceConfig: FlickerTestParameter,
             starveUiThread: Boolean
         ): FlickerTestParameter {
-            val newConfig = sourceConfig.config.toMutableMap()
-                .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread }
+            val newConfig =
+                sourceConfig.config.toMutableMap().also {
+                    it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
+                }
             val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
             return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
         }
 
         /**
-         * Creates the test configurations for seamless rotation based on the default rotation
-         * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an
-         * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app
-         * should starve the UI thread of not
+         * Creates the test configurations for seamless rotation based on the default rotation tests
+         * from [FlickerTestParameterFactory.getConfigRotationTests], but adding an additional flag
+         * ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
+         * starve the UI thread of not
          */
         @JvmStatic
         private fun getConfigurations(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests()
-                .flatMap { sourceConfig ->
-                    val defaultRun = createConfig(sourceConfig, starveUiThread = false)
-                    val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
-                    listOf(defaultRun, busyUiRun)
-                }
+            return FlickerTestParameterFactory.getInstance().getConfigRotationTests().flatMap {
+                sourceConfig ->
+                val defaultRun = createConfig(sourceConfig, starveUiThread = false)
+                val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
+                listOf(defaultRun, busyUiRun)
+            }
         }
 
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
+         * screen orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/test-apps/Android.bp b/tests/FlickerTests/test-apps/Android.bp
deleted file mode 100644
index e69de29..0000000
--- a/tests/FlickerTests/test-apps/Android.bp
+++ /dev/null
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 725963b..6e935d1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -15,41 +15,41 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.wm.flicker.testapp">
+          package="com.android.server.wm.flicker.testapp">
 
     <uses-sdk android:minSdkVersion="29"
-         android:targetSdkVersion="29"/>
+              android:targetSdkVersion="29"/>
     <application android:allowBackup="false"
-         android:supportsRtl="true">
+                 android:supportsRtl="true">
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
 
         <activity android:name=".SimpleActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:label="SimpleApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SimpleActivity"
+                  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=".ImeActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:label="ImeApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="ImeActivity"
+                  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=".ImeActivityAutoFocus"
-             android:theme="@style/CutoutShortEdges"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
-             android:windowSoftInputMode="stateVisible"
-             android:configChanges="orientation|screenSize"
-             android:label="ImeAppAutoFocus"
-             android:exported="true">
+                  android:theme="@style/CutoutShortEdges"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
+                  android:windowSoftInputMode="stateVisible"
+                  android:configChanges="orientation|screenSize"
+                  android:label="ImeAppAutoFocus"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -66,34 +66,34 @@
             </intent-filter>
         </activity>
         <activity android:name=".SeamlessRotationActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:configChanges="orientation|screenSize"
-             android:label="SeamlessApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="SeamlessActivity"
+                  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=".NonResizeableActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:resizeableActivity="false"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
-            android:label="NonResizeableApp"
-            android:exported="true"
-            android:showOnLockScreen="true">
+                  android:theme="@style/CutoutShortEdges"
+                  android:resizeableActivity="false"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
+                  android:label="NonResizeableActivity"
+                  android:exported="true"
+                  android:showOnLockScreen="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".ButtonActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:label="ButtonActivity"
-            android:exported="true">
+        <activity android:name=".LaunchNewActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="LaunchNewActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -111,55 +111,56 @@
             </intent-filter>
         </activity>
         <activity android:name=".DialogThemedActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
-            android:configChanges="orientation|screenSize"
-            android:theme="@style/DialogTheme"
-            android:label="DialogThemedActivity"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:theme="@style/DialogTheme"
+                  android:label="DialogThemedActivity"
+                  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=".PortraitOnlyActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:screenOrientation="portrait"
-            android:configChanges="orientation|screenSize"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:screenOrientation="portrait"
+                  android:configChanges="orientation|screenSize"
+                  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=".ImeEditorPopupDialogActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
-            android:configChanges="orientation|screenSize"
-            android:theme="@style/CutoutShortEdges"
-            android:label="ImeEditorPopupDialogActivity"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="ImeEditorPopupDialogActivity"
+                  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=".ShowWhenLockedActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:label="ShowWhenLockedActivity"
-            android:showWhenLocked="true"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="ShowWhenLockedActivity"
+                  android:showWhenLocked="true"
+                  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=".NotificationActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="NotificationActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -173,8 +174,8 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity
@@ -193,42 +194,110 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity android:name=".MailActivity"
-            android:exported="true"
-            android:label="MailApp"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
-            android:theme="@style/Theme.AppCompat.Light">
+                  android:exported="true"
+                  android:label="MailActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
+                  android:theme="@style/Theme.AppCompat.Light">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".GameActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
-            android:immersive="true"
-            android:theme="@android:style/Theme.NoTitleBar"
-            android:configChanges="screenSize"
-            android:label="GameApp"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
+                  android:immersive="true"
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:configChanges="screenSize"
+                  android:label="GameActivity"
+                  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=".PipActivity"
+                  android:resizeableActivity="true"
+                  android:supportsPictureInPicture="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:launchMode="singleTop"
+                  android:label="PipActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SplitScreenActivity"
+                  android:resizeableActivity="true"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SplitScreenPrimaryActivity"
+                  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=".SplitScreenSecondaryActivity"
+                  android:resizeableActivity="true"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SplitScreenSecondaryActivity"
+                  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=".SendNotificationActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SendNotificationActivity"
+                  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=".LaunchBubbleActivity"
+            android:label="LaunchBubbleActivity"
+            android:exported="true"
+            android:theme="@style/CutoutShortEdges"
+            android:launchMode="singleTop">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".BubbleActivity"
+            android:label="BubbleActivity"
+            android:exported="false"
+            android:theme="@style/CutoutShortEdges"
+            android:resizeableActivity="true"/>
         <service
             android:name=".AssistantInteractionSessionService"
             android:exported="true"
-            android:permission="android.permission.BIND_VOICE_INTERACTION" />
+            android:permission="android.permission.BIND_VOICE_INTERACTION"/>
         <service
             android:name=".AssistantRecognitionService"
             android:exported="true"
             android:label="Test Voice Interaction Service">
             <intent-filter>
-                <action android:name="android.speech.RecognitionService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.speech.RecognitionService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data
                 android:name="android.speech"
-                android:resource="@xml/recognition_service" />
+                android:resource="@xml/recognition_service"/>
         </service>
         <service
             android:name=".AssistantInteractionService"
@@ -236,12 +305,12 @@
             android:label="Test Voice Interaction Service"
             android:permission="android.permission.BIND_VOICE_INTERACTION">
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
             <meta-data
                 android:name="android.voice_interaction"
-                android:resource="@xml/interaction_service" />
+                android:resource="@xml/interaction_service"/>
         </service>
     </application>
-    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
 </manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
rename to tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
Binary files differ
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
new file mode 100644
index 0000000..4ea156d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+</vector>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
new file mode 100644
index 0000000..45ed98c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<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="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
similarity index 65%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
index f8b0ca3..7c7b2ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -1,19 +1,19 @@
 <?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.
--->
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
similarity index 100%
rename from tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
similarity index 67%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
index f23c464..553c7fe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
@@ -1,19 +1,19 @@
 <?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.
--->
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="match_parent"
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 2290983..f7ba45b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -1,19 +1,19 @@
 <?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.
--->
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
new file mode 100644
index 0000000..79e88e4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -0,0 +1,33 @@
+<?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="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/holo_green_light">
+
+    <TextView
+        android:id="@+id/SplitScreenTest"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical|center_horizontal"
+        android:textIsSelectable="true"
+        android:text="PrimaryActivity"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
new file mode 100644
index 0000000..ed9feaf
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
@@ -0,0 +1,32 @@
+<?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="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/holo_blue_light">
+
+    <TextView
+        android:id="@+id/SplitScreenTest"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical|center_horizontal"
+        android:text="SecondaryActivity"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
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 166e3ca..04a590d 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,9 +16,6 @@
 
 package com.android.server.wm.flicker.testapp;
 
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME;
-
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,8 +36,6 @@
     private static final String TAG = "ActivityEmbeddingMainActivity";
     private static final float DEFAULT_SPLIT_RATIO = 0.5f;
 
-    private ActivityEmbeddingComponent mEmbeddingComponent;
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -51,20 +46,24 @@
 
     /** R.id.launch_placeholder_split_button onClick */
     public void launchPlaceholderSplit(View view) {
-        startActivity(new Intent().setComponent(
-                ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME));
+        startActivity(
+                new Intent().setComponent(
+                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+                )
+        );
     }
 
     private void initializeSplitRules() {
-        mEmbeddingComponent = ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
-        if (mEmbeddingComponent == null) {
+        ActivityEmbeddingComponent embeddingComponent =
+                ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
+        if (embeddingComponent == null) {
             // Embedding not supported
             Log.d(TAG, "ActivityEmbedding is not supported on this device");
             finish();
             return;
         }
 
-        mEmbeddingComponent.setEmbeddingRules(getSplitRules());
+        embeddingComponent.setEmbeddingRules(getSplitRules());
     }
 
     private Set<EmbeddingRule> getSplitRules() {
@@ -72,10 +71,10 @@
 
         final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
                 new Intent().setComponent(
-                        ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME),
+                        ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
                 activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
                 intent -> intent.getComponent().equals(
-                        ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME),
+                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT),
                 windowMetrics -> true)
                 .setSplitRatio(DEFAULT_SPLIT_RATIO)
                 .build();
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 cae3df4..dcd8550 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
@@ -19,94 +19,175 @@
 import android.content.ComponentName;
 
 public class ActivityOptions {
-    public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
     public static final String FLICKER_APP_PACKAGE = "com.android.server.wm.flicker.testapp";
 
-    public static final String SEAMLESS_ACTIVITY_LAUNCHER_NAME = "SeamlessApp";
-    public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
+    public static class SimpleActivity {
+        public static final String LABEL = "SimpleActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".SimpleActivity");
+    }
 
-    public static final String IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME = "ImeAppAutoFocus";
-    public static final ComponentName IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+    public static class SeamlessRotation {
+        public static final String LABEL = "SeamlessRotationActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
 
-    public static final String IME_ACTIVITY_LAUNCHER_NAME = "ImeActivity";
-    public static final ComponentName IME_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
+    }
+
+    public static class Ime {
+        public static class Default {
+            public static final String LABEL = "ImeActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeActivity");
+        }
 
-    public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
-    public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static class AutoFocusActivity {
+            public static final String LABEL = "ImeAppAutoFocus";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+        }
+
+        public static class StateInitializeActivity {
+            public static final String LABEL = "ImeStateInitializeActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+        }
 
-    public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
-    public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".SimpleActivity");
-
-    public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp";
-    public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".NonResizeableActivity");
-
-    public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp";
-    public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ButtonActivity");
-
-    public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp";
-    public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
-
-    public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
-    public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".DialogThemedActivity");
-
-    public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity";
-    public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
-
-    public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME =
-            "ImeEditorPopupDialogActivity";
-    public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static class EditorPopupDialogActivity {
+            public static final String LABEL = "ImeEditorPopupDialogActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
+        }
+    }
 
-    public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
-    public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+    public static class NonResizeableActivity {
+        public static final String LABEL = "NonResizeableActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".NonResizeableActivity");
+    }
 
-    public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
-    public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".NotificationActivity");
+    public static class DialogThemedActivity {
+        public static final String LABEL = "DialogThemedActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".DialogThemedActivity");
+    }
 
-    public static final String ACTIVITY_EMBEDDING_LAUNCHER_NAME = "ActivityEmbeddingMainActivity";
-    public static final ComponentName ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+    public static class PortraitOnlyActivity {
+        public static final String LABEL = "PortraitOnlyActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
+        public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+    }
+
+    public static class ActivityEmbedding {
+        public static class MainActivity {
+            public static final String LABEL = "ActivityEmbeddingMainActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity");
-    public static final ComponentName
-            ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
-                    FLICKER_APP_PACKAGE,
-            FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
-    public static final ComponentName
-            ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
-                    FLICKER_APP_PACKAGE,
-            FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+        }
 
-    public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
-    public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
-            FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+        public static class PlaceholderPrimaryActivity {
+            public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
+        }
 
-    public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
-    public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                   FLICKER_APP_PACKAGE + ".GameActivity");
+        public static class PlaceholderSecondaryActivity {
+            public static final String LABEL = "ActivityEmbeddingPlaceholderSecondaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+        }
+    }
+
+    public static class Notification {
+        public static final String LABEL = "NotificationActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".NotificationActivity");
+    }
+
+    public static class Mail {
+        public static final String LABEL = "MailActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".MailActivity");
+    }
+
+    public static class ShowWhenLockedActivity {
+        public static final String LABEL = "ShowWhenLockedActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+    }
+
+    public static class LaunchNewTask {
+        public static final String LABEL = "LaunchNewTaskActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+    }
+
+    public static class Game {
+        public static final String LABEL = "GameActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".GameActivity");
+    }
+
+    public static class LaunchNewActivity {
+        public static final String LABEL = "LaunchNewActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchNewActivity");
+    }
+
+    public static class Pip {
+        // Test App > Pip Activity
+        public static final String LABEL = "PipActivity";
+        public static final String MENU_ACTION_NO_OP = "No-Op";
+        public static final String MENU_ACTION_ON = "On";
+        public static final String MENU_ACTION_OFF = "Off";
+        public static final String MENU_ACTION_CLEAR = "Clear";
+
+        // Intent action that this activity dynamically registers to enter picture-in-picture
+        public static final String ACTION_ENTER_PIP =
+                FLICKER_APP_PACKAGE + ".PipActivity.ENTER_PIP";
+        // Intent action that this activity dynamically registers to set requested orientation.
+        // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+        public static final String ACTION_SET_REQUESTED_ORIENTATION =
+                FLICKER_APP_PACKAGE + ".PipActivity.SET_REQUESTED_ORIENTATION";
+
+        // Calls enterPictureInPicture() on creation
+        public static final String EXTRA_ENTER_PIP = "enter_pip";
+        // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+        public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
+        // Adds a click listener to finish this activity when it is clicked
+        public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".PipActivity");
+    }
+
+    public static class SplitScreen {
+        public static class Primary {
+            public static final String LABEL = "SplitScreenPrimaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".SplitScreenActivity");
+        }
+
+        public static class Secondary {
+            public static final String LABEL = "SplitScreenSecondaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".SplitScreenSecondaryActivity");
+        }
+    }
+
+    public static class Bubbles {
+        public static class LaunchBubble {
+            public static final String LABEL = "LaunchBubbleActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".LaunchBubbleActivity");
+        }
+
+        public static class BubbleActivity {
+            public static final String LABEL = "BubbleActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".BubbleActivity");
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
index bc3bc75..58d7e67 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Activity;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
index 6cd93ef..c92b82b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Notification;
@@ -86,7 +86,7 @@
 
     }
 
-      private int getNextNotifyId() {
+    private int getNextNotifyId() {
         int id = mNextNotifyId;
         mNextNotifyId++;
         return id;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
index d4ae6c1..722929f 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
-import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION;
 
 import android.os.Bundle;
 
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
index 71fa66d..dea3444 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Activity;
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
similarity index 87%
rename from tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
index b42ac2a..e5710c8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
@@ -22,7 +22,7 @@
 import android.view.WindowManager;
 import android.widget.Button;
 
-public class ButtonActivity extends Activity {
+public class LaunchNewActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -30,11 +30,11 @@
         p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         getWindow().setAttributes(p);
-        setContentView(R.layout.activity_button);
+        setContentView(R.layout.activity_launch_new);
 
         Button button = findViewById(R.id.launch_second_activity);
         button.setOnClickListener(v -> {
-            Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class);
+            Intent intent = new Intent(LaunchNewActivity.this, SimpleActivity.class);
             startActivity(intent);
         });
     }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
index b31af38..a4dd575 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
@@ -50,12 +50,12 @@
         Intent resultIntent = new Intent(this, NotificationActivity.class);
         TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
         stackBuilder.addNextIntentWithParentStack(resultIntent);
-        PendingIntent resultPendingIntent =
-                stackBuilder.getPendingIntent(0,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-
+        PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0,
+                new Intent(this, NotificationActivity.class),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_notification)
+                .setWhen(System.currentTimeMillis())
                 .setContentTitle("Flicker Test Notification")
                 .setContentText("Flicker Test Notification")
                 // Set the intent that will fire when the user taps the notification
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 615b173..cdb1d42 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import static android.media.MediaMetadata.METADATA_KEY_TITLE;
 import static android.media.session.PlaybackState.ACTION_PAUSE;
@@ -24,10 +24,10 @@
 import static android.media.session.PlaybackState.STATE_PLAYING;
 import static android.media.session.PlaybackState.STATE_STOPPED;
 
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_SET_REQUESTED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_PIP_ORIENTATION;
 
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -127,7 +127,6 @@
                         break;
                     default:
                         Log.w(TAG, "Unhandled action=" + intent.getAction());
-                        return;
                 }
             }
         }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
index 5cf81cb..ce7a005 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
@@ -18,7 +18,7 @@
 
 import static android.os.SystemClock.sleep;
 
-import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
index 9c82eea..70196ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
index baa1e6f..a8ce8ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/tests/HandwritingIme/OWNERS b/tests/HandwritingIme/OWNERS
new file mode 100644
index 0000000..6bb4b17
--- /dev/null
+++ b/tests/HandwritingIme/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 34867
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
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 dc34cb6..2fd2368 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -29,6 +29,8 @@
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
@@ -38,6 +40,7 @@
 import android.widget.Toast;
 
 import java.util.Random;
+import java.util.function.IntConsumer;
 
 public class HandwritingIme extends InputMethodService {
 
@@ -47,8 +50,9 @@
     private static final int OP_SELECT = 1;
     private static final int OP_DELETE = 2;
     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 Window mInkWindow;
     private InkView mInk;
 
     static final String TAG = "HandwritingIme";
@@ -58,6 +62,7 @@
     private Spinner mRichGestureGranularitySpinner;
     private PointF mRichGestureStartPoint;
 
+    private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
 
     interface HandwritingFinisher {
         void finish();
@@ -91,48 +96,54 @@
             case MotionEvent.ACTION_UP: {
                 if (areRichGesturesEnabled()) {
                     HandwritingGesture gesture = null;
-                    switch(mRichGestureMode) {
+                    switch (mRichGestureMode) {
                         case OP_SELECT:
-                            SelectGesture.Builder builder = new SelectGesture.Builder();
-                            builder.setGranularity(mRichGestureGranularity)
-                                    .setSelectionArea(new RectF(mRichGestureStartPoint.x,
+                            gesture = new SelectGesture.Builder()
+                                    .setGranularity(mRichGestureGranularity)
+                                    .setSelectionArea(getSanitizedRectF(mRichGestureStartPoint.x,
                                             mRichGestureStartPoint.y, event.getX(), event.getY()))
-                                    .setFallbackText("fallback text");
-                            gesture = builder.build();
+                                    .setFallbackText("fallback text")
+                                    .build();
                             break;
                         case OP_DELETE:
-                            DeleteGesture.Builder builder1 = new DeleteGesture.Builder();
-                            builder1.setGranularity(mRichGestureGranularity)
-                                    .setDeletionArea(new RectF(mRichGestureStartPoint.x,
+                            gesture = new DeleteGesture.Builder()
+                                    .setGranularity(mRichGestureGranularity)
+                                    .setDeletionArea(getSanitizedRectF(mRichGestureStartPoint.x,
                                             mRichGestureStartPoint.y, event.getX(), event.getY()))
-                                    .setFallbackText("fallback text");
-                            gesture = builder1.build();
+                                    .setFallbackText("fallback text")
+                                    .build();
                             break;
                         case OP_INSERT:
-                            InsertGesture.Builder builder2 = new InsertGesture.Builder();
-                            builder2.setInsertionPoint(
-                                    new PointF(mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+                            gesture = new InsertGesture.Builder()
+                                    .setInsertionPoint(new PointF(
+                                            mRichGestureStartPoint.x, mRichGestureStartPoint.y))
                                     .setTextToInsert(" ")
-                                    .setFallbackText("fallback text");
-                            gesture = builder2.build();
-
+                                    .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;
                     }
                     if (gesture == null) {
                         // This shouldn't happen
                         Log.e(TAG, "Unrecognized gesture mode: " + mRichGestureMode);
                         return;
                     }
-                    InputConnection ic = getCurrentInputConnection();
-                    if (getCurrentInputStarted() && ic != null) {
-                        ic.performHandwritingGesture(gesture, null, null);
-                    } else {
-                        // This shouldn't happen
-                        Log.e(TAG, "No active InputConnection");
-                    }
-
-                    Log.d(TAG, "Sending RichGesture " + mRichGestureMode + " (Screen) Left: "
-                            + mRichGestureStartPoint.x + ", Top: " + mRichGestureStartPoint.y
-                            + ", Right: " + event.getX() + ", Bottom: " + event.getY());
+                    performGesture(gesture);
                 } else {
                     // insert random ASCII char
                     sendKeyChar((char) (56 + new Random().nextInt(66)));
@@ -148,6 +159,44 @@
         }
     }
 
+    /**
+     * sanitize values to support rectangles in all cases.
+     */
+    private RectF getSanitizedRectF(float left, float top, float right, float bottom) {
+        // swap values when left > right OR top > bottom.
+        if (left > right) {
+            float temp = left;
+            left = right;
+            right = temp;
+        }
+        if (top > bottom) {
+            float temp = top;
+            top = bottom;
+            bottom = temp;
+        }
+        // increment by a pixel so that RectF.isEmpty() isn't true.
+        if (left == right) {
+            right++;
+        }
+        if (top == bottom) {
+            bottom++;
+        }
+
+        RectF rectF = new RectF(left, top, right, bottom);
+        Log.d(TAG, "Sending RichGesture " + rectF.toShortString());
+        return rectF;
+    }
+
+    private void performGesture(HandwritingGesture gesture) {
+        InputConnection ic = getCurrentInputConnection();
+        if (getCurrentInputStarted() && ic != null) {
+            ic.performHandwritingGesture(gesture, Runnable::run, mResultConsumer);
+        } else {
+            // This shouldn't happen
+            Log.e(TAG, "No active InputConnection");
+        }
+    }
+
     @Override
     public View onCreateInputView() {
         Log.d(TAG, "onCreateInputView");
@@ -179,9 +228,14 @@
         mRichGestureModeSpinner = new Spinner(this);
         mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
         mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
-        String[] items =
-                new String[] { "Handwriting IME - Rich gesture disabled", "Rich gesture SELECT",
-                        "Rich gesture DELETE", "Rich gesture INSERT" };
+        String[] items = new String[] {
+                "Handwriting IME - Rich gesture disabled",
+                "Rich gesture SELECT",
+                "Rich gesture DELETE",
+                "Rich gesture INSERT",
+                "Rich gesture REMOVE SPACE",
+                "Rich gesture JOIN OR SPLIT",
+        };
         ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                 android.R.layout.simple_spinner_dropdown_item, items);
         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@@ -191,7 +245,7 @@
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 mRichGestureMode = position;
                 mRichGestureGranularitySpinner.setEnabled(
-                        mRichGestureMode != OP_INSERT && mRichGestureMode != OP_NONE);
+                        mRichGestureMode == OP_SELECT || mRichGestureMode == OP_DELETE);
                 Log.d(TAG, "Setting RichGesture Mode " + mRichGestureMode);
             }
 
@@ -247,8 +301,8 @@
     public boolean onStartStylusHandwriting() {
         Log.d(TAG, "onStartStylusHandwriting ");
         Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show();
-        mInkWindow = getStylusHandwritingWindow();
-        mInkWindow.setContentView(mInk, mInk.getLayoutParams());
+        Window inkWindow = getStylusHandwritingWindow();
+        inkWindow.setContentView(mInk, mInk.getLayoutParams());
         return true;
     }
 
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
index 94b1f86..e94c79e 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -19,13 +19,11 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
@@ -33,9 +31,8 @@
     private static final long FINISH_TIMEOUT = 1500;
     private final HandwritingIme.HandwritingFinisher mHwCanceller;
     private final HandwritingIme.StylusConsumer mConsumer;
-    private final int mTopInset;
-    private Paint mPaint;
-    private Path  mPath;
+    private final Paint mPaint;
+    private final Path mPath;
     private float mX, mY;
     private static final float STYLUS_MOVE_TOLERANCE = 1;
     private Runnable mFinishRunnable;
@@ -59,12 +56,8 @@
 
         WindowManager wm = context.getSystemService(WindowManager.class);
         WindowMetrics metrics =  wm.getCurrentWindowMetrics();
-        Insets insets = metrics.getWindowInsets()
-                .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
         setLayoutParams(new ViewGroup.LayoutParams(
-                metrics.getBounds().width() - insets.left - insets.right,
-                metrics.getBounds().height() - insets.top - insets.bottom));
-        mTopInset = insets.top;
+                metrics.getBounds().width(), metrics.getBounds().height()));
     }
 
     @Override
@@ -76,14 +69,12 @@
     }
 
     private void stylusStart(float x, float y) {
-        y = y - mTopInset;
         mPath.moveTo(x, y);
         mX = x;
         mY = y;
     }
 
     private void stylusMove(float x, float y) {
-        y = y - mTopInset;
         float dx = Math.abs(x - mX);
         float dy = Math.abs(y - mY);
         if (mPath.isEmpty()) {
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index b7c4c5b..f2234fb 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -51,6 +51,7 @@
     data: [
         ":com.android.apex.apkrollback.test_v1",
         ":test.rebootless_apex_v1",
+        ":RollbackTest",
     ],
 }
 
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
index b41ee3a..046174c 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -19,11 +19,7 @@
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.hardware.display.DisplayManager
-import android.os.IBinder
 import android.util.AttributeSet
-import android.util.Log
-import android.view.SurfaceControl
-import android.view.SurfaceControlHdrLayerInfoListener
 import android.view.Window
 import android.widget.Button
 import android.widget.LinearLayout
@@ -39,7 +35,6 @@
     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
         displayManager = context.getSystemService(DisplayManager::class.java)!!
         displayId = context.getDisplayId()
-        displayToken = SurfaceControl.getInternalDisplayToken()
     }
 
     private var window: Window? = null
@@ -47,7 +42,6 @@
     private val displayManager: DisplayManager
     private var targetSdrWhitePointIndex = 0
     private var displayId: Int
-    private var displayToken: IBinder
 
     private val whitePoint get() = SDR_WHITE_POINTS[targetSdrWhitePointIndex]
 
@@ -115,36 +109,10 @@
         // Imperfect, but close enough, synchronization by waiting for frame commit to set the value
         viewTreeObserver.registerFrameCommitCallback {
             try {
-                SurfaceControl.setDisplayBrightness(displayToken, level)
                 displayManager.setTemporaryBrightness(displayId, level)
             } catch (ex: Exception) {
                 // Ignore a permission denied rejection - it doesn't meaningfully change much
             }
         }
     }
-
-    private val listener = object : SurfaceControlHdrLayerInfoListener() {
-        override fun onHdrInfoChanged(
-            displayToken: IBinder?,
-            numberOfHdrLayers: Int,
-            maxW: Int,
-            maxH: Int,
-            flags: Int
-        ) {
-            Log.d("HDRInfo", "onHdrInfoChanged: numLayer = $numberOfHdrLayers ($maxW x $maxH)" +
-                    ", flags = $flags")
-        }
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-
-        threadedRenderer?.setColorMode(window!!.colorMode, whitePoint)
-        listener.register(displayToken)
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        listener.unregister(displayToken)
-    }
-}
\ No newline at end of file
+}
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 052ce3a..7a2af72 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -153,6 +153,20 @@
     }
 
     @Test
+    public void testGetDefaultMmsApplication() {
+        assertEquals(TEST_COMPONENT_NAME,
+                SmsApplication.getDefaultMmsApplicationAsUser(mContext, false,
+                        UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testGetDefaultExternalTelephonyProviderChangedApplication() {
+        assertEquals(TEST_COMPONENT_NAME,
+                SmsApplication.getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
+                        false, UserHandle.USER_SYSTEM));
+    }
+
+    @Test
     public void testGetDefaultSmsApplicationWithAppOpsFix() throws Exception {
         when(mAppOpsManager.unsafeCheckOp(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
                 TEST_COMPONENT_NAME.getPackageName()))
diff --git a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
index 3885486..2001c04 100644
--- a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
+++ b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
@@ -1,454 +1,458 @@
-/*

- * 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.internal;

-

-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

-

-import android.app.Activity;

-import android.graphics.Rect;

-import android.os.Bundle;

-import android.os.Message;

-import android.os.ParcelFileDescriptor;

-import android.os.Process;

-import android.os.SystemClock;

-import android.util.Log;

-

-import androidx.test.filters.LargeTest;

-

-import com.android.internal.util.function.pooled.PooledConsumer;

-import com.android.internal.util.function.pooled.PooledLambda;

-import com.android.internal.util.function.pooled.PooledPredicate;

-

-import org.junit.Assume;

-import org.junit.Rule;

-import org.junit.Test;

-import org.junit.rules.TestRule;

-import org.junit.runners.model.Statement;

-

-import java.io.BufferedReader;

-import java.io.IOException;

-import java.io.InputStreamReader;

-import java.util.ArrayList;

-import java.util.Arrays;

-import java.util.List;

-import java.util.concurrent.CountDownLatch;

-import java.util.function.Consumer;

-import java.util.function.Predicate;

-import java.util.regex.Matcher;

-import java.util.regex.Pattern;

-

-/** Compares the performance of regular lambda and pooled lambda. */

-@LargeTest

-public class LambdaPerfTest {

-    private static final boolean DEBUG = false;

-    private static final String TAG = LambdaPerfTest.class.getSimpleName();

-

-    private static final String LAMBDA_FORM_REGULAR = "regular";

-    private static final String LAMBDA_FORM_POOLED = "pooled";

-

-    private static final int WARMUP_ITERATIONS = 1000;

-    private static final int TEST_ITERATIONS = 3000000;

-    private static final int TASK_COUNT = 10;

-    private static final long DELAY_AFTER_BENCH_MS = 1000;

-

-    private String mMethodName;

-

-    private final Bundle mTestResults = new Bundle();

-    private final ArrayList<Task> mTasks = new ArrayList<>();

-

-    // The member fields are used to ensure lambda capturing. They don't have the actual meaning.

-    private final Task mTask = new Task();

-    private final Rect mBounds = new Rect();

-    private int mTaskId;

-    private long mTime;

-    private boolean mTop;

-

-    @Rule

-    public final TestRule mRule = (base, description) -> new Statement() {

-        @Override

-        public void evaluate() throws Throwable {

-            mMethodName = description.getMethodName();

-            mTasks.clear();

-            for (int i = 0; i < TASK_COUNT; i++) {

-                final Task t = new Task();

-                mTasks.add(t);

-            }

-            base.evaluate();

-

-            getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);

-        }

-    };

-

-    @Test

-    public void test1ParamConsumer() {

-        evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));

-        evaluate(LAMBDA_FORM_POOLED, () -> {

-            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,

-                    PooledLambda.__(Task.class), mTask);

-            forAllTask(c);

-            c.recycle();

-        });

-    }

-

-    @Test

-    public void test2PrimitiveParamsConsumer() {

-        // Not in Integer#IntegerCache (-128~127) for autoboxing, that will create new object.

-        mTaskId = 12345;

-        mTime = 54321;

-

-        evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));

-        evaluate(LAMBDA_FORM_POOLED, () -> {

-            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,

-                    PooledLambda.__(Task.class), mTaskId, mTime);

-            forAllTask(c);

-            c.recycle();

-        });

-    }

-

-    @Test

-    public void test3ParamsPredicate() {

-        mTop = true;

-        // In Integer#IntegerCache.

-        mTaskId = 10;

-

-        evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));

-        evaluate(LAMBDA_FORM_POOLED, () -> {

-            final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,

-                    PooledLambda.__(Task.class), mBounds, mTop, mTaskId);

-            handleTask(c);

-            c.recycle();

-        });

-    }

-

-    @Test

-    public void testMessage() {

-        evaluate(LAMBDA_FORM_REGULAR, () -> {

-            final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));

-            m.getCallback().run();

-            m.recycle();

-        });

-        evaluate(LAMBDA_FORM_POOLED, () -> {

-            final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);

-            m.getCallback().run();

-            m.recycle();

-        });

-    }

-

-    @Test

-    public void testRunnable() {

-        evaluate(LAMBDA_FORM_REGULAR, () -> {

-            final Runnable r = mTask::doSomething;

-            r.run();

-        });

-        evaluate(LAMBDA_FORM_POOLED, () -> {

-            final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();

-            r.run();

-        });

-    }

-

-    @Test

-    public void testMultiThread() {

-        final int numThread = 3;

-

-        final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));

-        final Runnable[] regularActions = new Runnable[numThread];

-        Arrays.fill(regularActions, regularAction);

-        evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);

-

-        final Runnable pooledAction = () -> {

-            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,

-                    PooledLambda.__(Task.class), mTask);

-            forAllTask(c);

-            c.recycle();

-        };

-        final Runnable[] pooledActions = new Runnable[numThread];

-        Arrays.fill(pooledActions, pooledAction);

-        evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);

-    }

-

-    private void forAllTask(Consumer<Task> callback) {

-        for (int i = mTasks.size() - 1; i >= 0; i--) {

-            callback.accept(mTasks.get(i));

-        }

-    }

-

-    private void handleTask(Predicate<Task> callback) {

-        for (int i = mTasks.size() - 1; i >= 0; i--) {

-            final Task task = mTasks.get(i);

-            if (callback.test(task)) {

-                return;

-            }

-        }

-    }

-

-    private void evaluate(String title, Runnable action) {

-        for (int i = 0; i < WARMUP_ITERATIONS; i++) {

-            action.run();

-        }

-        performGc();

-

-        final GcStatus startGcStatus = getGcStatus();

-        final long startTime = SystemClock.elapsedRealtime();

-        for (int i = 0; i < TEST_ITERATIONS; i++) {

-            action.run();

-        }

-        evaluateResult(title, startGcStatus, startTime);

-    }

-

-    private void evaluateMultiThread(String title, Runnable[] actions) {

-        performGc();

-

-        final CountDownLatch latch = new CountDownLatch(actions.length);

-        final GcStatus startGcStatus = getGcStatus();

-        final long startTime = SystemClock.elapsedRealtime();

-        for (Runnable action : actions) {

-            new Thread() {

-                @Override

-                public void run() {

-                    for (int i = 0; i < TEST_ITERATIONS; i++) {

-                        action.run();

-                    }

-                    latch.countDown();

-                };

-            }.start();

-        }

-        try {

-            latch.await();

-        } catch (InterruptedException ignored) {

-        }

-        evaluateResult(title, startGcStatus, startTime);

-    }

-

-    private void evaluateResult(String title, GcStatus startStatus, long startTime) {

-        final float elapsed = SystemClock.elapsedRealtime() - startTime;

-        // Sleep a while to see if GC may happen.

-        SystemClock.sleep(DELAY_AFTER_BENCH_MS);

-        final GcStatus endStatus = getGcStatus();

-        final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);

-        Log.i(TAG, mMethodName + "_" + title + " execution time: "

-                + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"

-                + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"

-                + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");

-    }

-

-    /** Cleans the test environment. */

-    private static void performGc() {

-        System.gc();

-        System.runFinalization();

-        System.gc();

-    }

-

-    private static GcStatus getGcStatus() {

-        if (DEBUG) {

-            Log.i(TAG, "===== Read GC dump =====");

-        }

-        final GcStatus status = new GcStatus();

-        final List<String> vmDump = getVmDump();

-        Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());

-        for (String line : vmDump) {

-            status.visit(line);

-            if (line.startsWith("DALVIK THREADS")) {

-                break;

-            }

-        }

-        return status;

-    }

-

-    private static List<String> getVmDump() {

-        final int myPid = Process.myPid();

-        // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.

-        Process.sendSignal(myPid, Process.SIGNAL_QUIT);

-        // Give a chance to handle the signal.

-        SystemClock.sleep(100);

-

-        String dump = null;

-        final String pattern = myPid + " written to: ";

-        final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");

-        for (int i = logs.size() - 1; i >= 0; i--) {

-            final String log = logs.get(i);

-            // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07

-            final int pos = log.indexOf(pattern);

-            if (pos > 0) {

-                dump = log.substring(pattern.length() + pos);

-                break;

-            }

-        }

-

-        Assume.assumeNotNull("Unable to find VM dump", dump);

-        // It requires system or root uid to read the trace.

-        return shell("cat " + dump);

-    }

-

-    private static List<String> shell(String command) {

-        final ParcelFileDescriptor.AutoCloseInputStream stream =

-                new ParcelFileDescriptor.AutoCloseInputStream(

-                getInstrumentation().getUiAutomation().executeShellCommand(command));

-        final ArrayList<String> lines = new ArrayList<>();

-        try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {

-            String line;

-            while ((line = br.readLine()) != null) {

-                lines.add(line);

-            }

-        } catch (IOException e) {

-            throw new RuntimeException(e);

-        }

-        return lines;

-    }

-

-    /** An empty class which provides some methods with different type arguments. */

-    static class Task {

-        void doSomething() {

-        }

-

-        void doSomething(Task t) {

-        }

-

-        void doSomething(int taskId, long time) {

-        }

-

-        boolean doSomething(Rect bounds, boolean top, int taskId) {

-            return false;

-        }

-    }

-

-    static class ValPattern {

-        static final int TYPE_COUNT = 0;

-        static final int TYPE_TIME = 1;

-        static final String PATTERN_COUNT = "(\\d+)";

-        static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";

-        final String mRawPattern;

-        final Pattern mPattern;

-        final int mType;

-

-        int mIntValue;

-        float mFloatValue;

-

-        ValPattern(String p, int type) {

-            mRawPattern = p;

-            mPattern = Pattern.compile(

-                    p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");

-            mType = type;

-        }

-

-        boolean visit(String line) {

-            final Matcher matcher = mPattern.matcher(line);

-            if (!matcher.matches()) {

-                return false;

-            }

-            final String value = matcher.group(1);

-            if (value == null) {

-                return false;

-            }

-            if (mType == TYPE_COUNT) {

-                mIntValue = Integer.parseInt(value);

-                return true;

-            }

-            final float time = Float.parseFloat(value);

-            final String unit = matcher.group(2);

-            if (unit == null) {

-                return false;

-            }

-            // Refer to art/libartbase/base/time_utils.cc

-            switch (unit) {

-                case "s":

-                    mFloatValue = time * 1000;

-                    break;

-                case "ms":

-                    mFloatValue = time;

-                    break;

-                case "us":

-                    mFloatValue = time / 1000;

-                    break;

-                case "ns":

-                    mFloatValue = time / 1000 / 1000;

-                    break;

-                default:

-                    throw new IllegalArgumentException();

-            }

-

-            return true;

-        }

-

-        @Override

-        public String toString() {

-            return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);

-        }

-    }

-

-    /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */

-    private static class GcStatus {

-        private static final int TOTAL_GC_TIME_INDEX = 1;

-        private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;

-

-        // Refer to art/runtime/gc/heap.cc

-        final ValPattern[] mPatterns = {

-                new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),

-                new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),

-                new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),

-                new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),

-                new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),

-                new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),

-                new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),

-                new ValPattern("concurrent copying paused:  Sum: ", ValPattern.TYPE_TIME),

-                new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),

-                new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),

-                new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),

-        };

-

-        void visit(String dumpLine) {

-            for (ValPattern p : mPatterns) {

-                if (p.visit(dumpLine)) {

-                    if (DEBUG) {

-                        Log.i(TAG, "  " + p);

-                    }

-                }

-            }

-        }

-

-        GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {

-            Log.i(TAG, "===== GC status of " + title + " =====");

-            final GcInfo info = new GcInfo();

-            for (int i = 0; i < mPatterns.length; i++) {

-                final ValPattern p = mPatterns[i];

-                if (p.mType == ValPattern.TYPE_COUNT) {

-                    final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;

-                    Log.i(TAG, "  " + p.mRawPattern + diff);

-                    if (diff > 0) {

-                        result.putInt("[" + title + "] " + p.mRawPattern, diff);

-                    }

-                    continue;

-                }

-                final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;

-                Log.i(TAG, "  " + p.mRawPattern + diff + "ms");

-                if (diff > 0) {

-                    result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);

-                }

-                if (i == TOTAL_GC_TIME_INDEX) {

-                    info.mTotalGcTime = diff;

-                } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {

-                    info.mTotalGcPausedTime = diff;

-                }

-            }

-            return info;

-        }

-    }

-

-    private static class GcInfo {

-        float mTotalGcTime;

-        float mTotalGcPausedTime;

-    }

-}

+/*
+ * 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.internal;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Compares the performance of regular lambda and pooled lambda. */
+@LargeTest
+public class LambdaPerfTest {
+    private static final boolean DEBUG = false;
+    private static final String TAG = LambdaPerfTest.class.getSimpleName();
+
+    private static final String LAMBDA_FORM_REGULAR = "regular";
+    private static final String LAMBDA_FORM_POOLED = "pooled";
+
+    private static final int WARMUP_ITERATIONS = 1000;
+    private static final int TEST_ITERATIONS = 3000000;
+    private static final int TASK_COUNT = 10;
+    private static final long DELAY_AFTER_BENCH_MS = 1000;
+
+    private String mMethodName;
+
+    private final Bundle mTestResults = new Bundle();
+    private final ArrayList<Task> mTasks = new ArrayList<>();
+
+    // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
+    private final Task mTask = new Task();
+    private final Rect mBounds = new Rect();
+    private int mTaskId;
+    private long mTime;
+    private boolean mTop;
+
+    @Rule
+    public final TestRule mRule = (base, description) -> new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+            mMethodName = description.getMethodName();
+            mTasks.clear();
+            for (int i = 0; i < TASK_COUNT; i++) {
+                final Task t = new Task();
+                mTasks.add(t);
+            }
+            base.evaluate();
+
+            getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
+        }
+    };
+
+    @Test
+    public void test1ParamConsumer() {
+        evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
+        evaluate(LAMBDA_FORM_POOLED, () -> {
+            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+                    PooledLambda.__(Task.class), mTask);
+            forAllTask(c);
+            c.recycle();
+        });
+    }
+
+    @Test
+    public void test2PrimitiveParamsConsumer() {
+        // Not in Integer#IntegerCache (-128~127) for autoboxing, that may create new object.
+        mTaskId = 12345;
+        mTime = 54321;
+
+        evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
+        evaluate(LAMBDA_FORM_POOLED, () -> {
+            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+                    PooledLambda.__(Task.class), mTaskId, mTime);
+            forAllTask(c);
+            c.recycle();
+        });
+    }
+
+    @Test
+    public void test3ParamsPredicate() {
+        mTop = true;
+        // In Integer#IntegerCache.
+        mTaskId = 10;
+
+        evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
+        evaluate(LAMBDA_FORM_POOLED, () -> {
+            final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
+                    PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
+            handleTask(c);
+            c.recycle();
+        });
+    }
+
+    @Test
+    public void testMessage() {
+        evaluate(LAMBDA_FORM_REGULAR, () -> {
+            final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
+            m.getCallback().run();
+            m.recycle();
+        });
+        evaluate(LAMBDA_FORM_POOLED, () -> {
+            final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
+            m.getCallback().run();
+            m.recycle();
+        });
+    }
+
+    @Test
+    public void testRunnable() {
+        evaluate(LAMBDA_FORM_REGULAR, () -> {
+            final Runnable r = mTask::doSomething;
+            r.run();
+        });
+        evaluate(LAMBDA_FORM_POOLED, () -> {
+            final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
+            r.run();
+        });
+    }
+
+    @Test
+    public void testMultiThread() {
+        final int numThread = 3;
+
+        final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
+        final Runnable[] regularActions = new Runnable[numThread];
+        Arrays.fill(regularActions, regularAction);
+        evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
+
+        final Runnable pooledAction = () -> {
+            final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+                    PooledLambda.__(Task.class), mTask);
+            forAllTask(c);
+            c.recycle();
+        };
+        final Runnable[] pooledActions = new Runnable[numThread];
+        Arrays.fill(pooledActions, pooledAction);
+        evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
+    }
+
+    private void forAllTask(Consumer<Task> callback) {
+        for (int i = mTasks.size() - 1; i >= 0; i--) {
+            callback.accept(mTasks.get(i));
+        }
+    }
+
+    private void handleTask(Predicate<Task> callback) {
+        for (int i = mTasks.size() - 1; i >= 0; i--) {
+            final Task task = mTasks.get(i);
+            if (callback.test(task)) {
+                return;
+            }
+        }
+    }
+
+    private void evaluate(String title, Runnable action) {
+        for (int i = 0; i < WARMUP_ITERATIONS; i++) {
+            action.run();
+        }
+        performGc();
+
+        final GcStatus startGcStatus = getGcStatus();
+        final long startTime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < TEST_ITERATIONS; i++) {
+            action.run();
+        }
+        evaluateResult(title, startGcStatus, startTime);
+    }
+
+    private void evaluateMultiThread(String title, Runnable[] actions) {
+        performGc();
+
+        final CountDownLatch latch = new CountDownLatch(actions.length);
+        final GcStatus startGcStatus = getGcStatus();
+        final long startTime = SystemClock.elapsedRealtime();
+        for (Runnable action : actions) {
+            new Thread() {
+                @Override
+                public void run() {
+                    for (int i = 0; i < TEST_ITERATIONS; i++) {
+                        action.run();
+                    }
+                    latch.countDown();
+                };
+            }.start();
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException ignored) {
+        }
+        evaluateResult(title, startGcStatus, startTime);
+    }
+
+    private void evaluateResult(String title, GcStatus startStatus, long startTime) {
+        final float elapsed = SystemClock.elapsedRealtime() - startTime;
+        // Sleep a while to see if GC may happen.
+        SystemClock.sleep(DELAY_AFTER_BENCH_MS);
+        final GcStatus endStatus = getGcStatus();
+        final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
+        mTestResults.putFloat("[" + title + "-execution-time]", elapsed);
+        Log.i(TAG, mMethodName + "_" + title + " execution time: "
+                + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
+                + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
+                + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
+    }
+
+    /** Cleans the test environment. */
+    private static void performGc() {
+        System.gc();
+        System.runFinalization();
+        System.gc();
+    }
+
+    private static GcStatus getGcStatus() {
+        if (DEBUG) {
+            Log.i(TAG, "===== Read GC dump =====");
+        }
+        final GcStatus status = new GcStatus();
+        final List<String> vmDump = getVmDump();
+        Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
+        for (String line : vmDump) {
+            status.visit(line);
+            if (line.startsWith("DALVIK THREADS")) {
+                break;
+            }
+        }
+        return status;
+    }
+
+    private static List<String> getVmDump() {
+        final int myPid = Process.myPid();
+        // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
+        Process.sendSignal(myPid, Process.SIGNAL_QUIT);
+        // Give a chance to handle the signal.
+        SystemClock.sleep(100);
+
+        String dump = null;
+        final String pattern = myPid + " written to: ";
+        final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
+        for (int i = logs.size() - 1; i >= 0; i--) {
+            final String log = logs.get(i);
+            // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
+            final int pos = log.indexOf(pattern);
+            if (pos > 0) {
+                dump = log.substring(pattern.length() + pos);
+                if (!dump.startsWith("/data/anr/")) {
+                    dump = "/data/anr/" + dump;
+                }
+                break;
+            }
+        }
+
+        Assume.assumeNotNull("Unable to find VM dump", dump);
+        // It requires system or root uid to read the trace.
+        return shell("cat " + dump);
+    }
+
+    private static List<String> shell(String command) {
+        final ParcelFileDescriptor.AutoCloseInputStream stream =
+                new ParcelFileDescriptor.AutoCloseInputStream(
+                getInstrumentation().getUiAutomation().executeShellCommand(command));
+        final ArrayList<String> lines = new ArrayList<>();
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                lines.add(line);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return lines;
+    }
+
+    /** An empty class which provides some methods with different type arguments. */
+    static class Task {
+        void doSomething() {
+        }
+
+        void doSomething(Task t) {
+        }
+
+        void doSomething(int taskId, long time) {
+        }
+
+        boolean doSomething(Rect bounds, boolean top, int taskId) {
+            return false;
+        }
+    }
+
+    static class ValPattern {
+        static final int TYPE_COUNT = 0;
+        static final int TYPE_TIME = 1;
+        static final String PATTERN_COUNT = "(\\d+)";
+        static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
+        final String mRawPattern;
+        final Pattern mPattern;
+        final int mType;
+
+        int mIntValue;
+        float mFloatValue;
+
+        ValPattern(String p, int type) {
+            mRawPattern = p;
+            mPattern = Pattern.compile(
+                    p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
+            mType = type;
+        }
+
+        boolean visit(String line) {
+            final Matcher matcher = mPattern.matcher(line);
+            if (!matcher.matches()) {
+                return false;
+            }
+            final String value = matcher.group(1);
+            if (value == null) {
+                return false;
+            }
+            if (mType == TYPE_COUNT) {
+                mIntValue = Integer.parseInt(value);
+                return true;
+            }
+            final float time = Float.parseFloat(value);
+            final String unit = matcher.group(2);
+            if (unit == null) {
+                return false;
+            }
+            // Refer to art/libartbase/base/time_utils.cc
+            switch (unit) {
+                case "s":
+                    mFloatValue = time * 1000;
+                    break;
+                case "ms":
+                    mFloatValue = time;
+                    break;
+                case "us":
+                    mFloatValue = time / 1000;
+                    break;
+                case "ns":
+                    mFloatValue = time / 1000 / 1000;
+                    break;
+                default:
+                    throw new IllegalArgumentException();
+            }
+
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
+        }
+    }
+
+    /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
+    private static class GcStatus {
+        private static final int TOTAL_GC_TIME_INDEX = 1;
+        private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
+
+        // Refer to art/runtime/gc/heap.cc
+        final ValPattern[] mPatterns = {
+                new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
+                new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
+                new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
+                new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
+                new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
+                new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
+                new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
+                new ValPattern("concurrent copying paused:  Sum: ", ValPattern.TYPE_TIME),
+                new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
+                new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
+                new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
+        };
+
+        void visit(String dumpLine) {
+            for (ValPattern p : mPatterns) {
+                if (p.visit(dumpLine)) {
+                    if (DEBUG) {
+                        Log.i(TAG, "  " + p);
+                    }
+                }
+            }
+        }
+
+        GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
+            Log.i(TAG, "===== GC status of " + title + " =====");
+            final GcInfo info = new GcInfo();
+            for (int i = 0; i < mPatterns.length; i++) {
+                final ValPattern p = mPatterns[i];
+                if (p.mType == ValPattern.TYPE_COUNT) {
+                    final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
+                    Log.i(TAG, "  " + p.mRawPattern + diff);
+                    if (diff > 0) {
+                        result.putInt("[" + title + "] " + p.mRawPattern, diff);
+                    }
+                    continue;
+                }
+                final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
+                Log.i(TAG, "  " + p.mRawPattern + diff + "ms");
+                if (diff > 0) {
+                    result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
+                }
+                if (i == TOTAL_GC_TIME_INDEX) {
+                    info.mTotalGcTime = diff;
+                } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
+                    info.mTotalGcPausedTime = diff;
+                }
+            }
+            return info;
+        }
+    }
+
+    private static class GcInfo {
+        float mTotalGcTime;
+        float mTotalGcPausedTime;
+    }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 54b3c40..f924b2e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
 
@@ -276,7 +277,6 @@
     @Test
     public void testSystemReady() throws Exception {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
 
         verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
         verify(mSubscriptionTracker).register();
@@ -494,8 +494,10 @@
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
-        mTestLooper.dispatchAll();
 
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
         verify(vcn).teardownAsynchronously();
         verify(mMockPolicyListener).onPolicyChanged();
     }
@@ -521,6 +523,92 @@
         assertEquals(0, mVcnMgmtSvc.getAllVcns().size());
     }
 
+    /**
+     * Tests an intermediate state where carrier privileges are marked as lost before active data
+     * subId changes during a SIM ejection.
+     *
+     * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to
+     * immediately.
+     */
+    @Test
+    public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate privileges lost
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_2,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                INVALID_SUBSCRIPTION_ID,
+                null /* activeDataSubscriptionGroup */,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+
+        // Simulate new SIM loaded right during teardown delay.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+
+        // Verify that even after the full timeout duration, the VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn, never()).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
+
+        // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
+        // vcnInstance.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+        final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Verify that new instance was different, and the old one was torn down
+        assertTrue(oldInstance != newInstance);
+        verify(oldInstance).teardownAsynchronously();
+
+        // Verify that even after the full timeout duration, the new VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(newInstance, never()).teardownAsynchronously();
+    }
+
     @Test
     public void testPackageChangeListenerRegistered() throws Exception {
         verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> {
@@ -910,8 +998,6 @@
     private void setupSubscriptionAndStartVcn(
             int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
-
         triggerSubscriptionTrackerCbAndGetSnapshot(
                 subGrp,
                 Collections.singleton(subGrp),
@@ -1007,7 +1093,6 @@
 
     private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
 
         final ArgumentCaptor<NetworkCallback> captor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
@@ -1252,14 +1337,15 @@
                 true /* isActive */,
                 true /* hasCarrierPrivileges */);
 
-        // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes
-        // inactive.
+        // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown
+        // timeout so the VCN goes inactive.
         final TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(
                         TEST_UUID_1,
                         Collections.singleton(TEST_UUID_1),
                         Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
                         false /* hasCarrierPrivileges */);
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
         mTestLooper.dispatchAll();
 
         // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7efe3c3..aa337e5 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -105,6 +105,7 @@
         "format/Container.cpp",
         "format/binary/BinaryResourceParser.cpp",
         "format/binary/ResChunkPullParser.cpp",
+        "format/binary/ResEntryWriter.cpp",
         "format/binary/TableFlattener.cpp",
         "format/binary/XmlFlattener.cpp",
         "format/proto/ProtoDeserialize.cpp",
@@ -130,7 +131,7 @@
         "optimize/MultiApkGenerator.cpp",
         "optimize/ResourceDeduper.cpp",
         "optimize/ResourceFilter.cpp",
-        "optimize/ResourcePathShortener.cpp",
+        "optimize/Obfuscator.cpp",
         "optimize/VersionCollapser.cpp",
         "process/SymbolTable.cpp",
         "split/TableSplitter.cpp",
@@ -161,6 +162,7 @@
         "ApkInfo.proto",
         "Configuration.proto",
         "Resources.proto",
+        "ResourceMetadata.proto",
         "ResourcesInternal.proto",
         "ValueTransformer.cpp",
     ],
@@ -218,6 +220,7 @@
     srcs: [
         "Configuration.proto",
         "ResourcesInternal.proto",
+        "ResourceMetadata.proto",
         "Resources.proto",
     ],
     out: ["aapt2-protos.zip"],
diff --git a/tools/aapt2/ResourceMetadata.proto b/tools/aapt2/ResourceMetadata.proto
new file mode 100644
index 0000000..8eca54c
--- /dev/null
+++ b/tools/aapt2/ResourceMetadata.proto
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 = "proto3";
+
+package aapt.pb;
+
+option java_package = "com.android.aapt";
+option java_multiple_files = true;
+
+message ResourceMappings {
+  ShortenedPathsMap shortened_paths = 1;
+  CollapsedNamesMap collapsed_names = 2;
+}
+
+// Metadata relating to "aapt2 optimize --shorten-resource-paths"
+message ShortenedPathsMap {
+  // Maps shorted paths (e.g. "res/foo.xml") to their original names (e.g.
+  // "res/xml/file_with_long_name.xml").
+  message ResourcePathMapping {
+    string shortened_path = 1;
+    string original_path = 2;
+  }
+  repeated ResourcePathMapping resource_paths = 1;
+}
+
+// Metadata relating to "aapt2 optimize --collapse-resource-names"
+message CollapsedNamesMap {
+  // Maps resource IDs (e.g. 0x7f123456) to their original names (e.g.
+  // "package:type/entry").
+  message ResourceNameMapping {
+    uint32 id = 1;
+    string name = 2;
+  }
+  repeated ResourceNameMapping resource_names = 1;
+}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 945f45b..41c7435 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -43,8 +43,9 @@
 static std::optional<ResourceNamedType> ToResourceNamedType(const char16_t* type16,
                                                             const char* type, size_t type_len) {
   std::optional<ResourceNamedTypeRef> parsed_type;
+  std::string converted;
   if (type16) {
-    auto converted = android::util::Utf16ToUtf8(StringPiece16(type16, type_len));
+    converted = android::util::Utf16ToUtf8(StringPiece16(type16, type_len));
     parsed_type = ParseResourceNamedType(converted);
   } else if (type) {
     parsed_type = ParseResourceNamedType(StringPiece(type, type_len));
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index cf58f98..aeedf8b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -395,6 +395,12 @@
                                     << output_format_.value());
     return 1;
   }
+  if (enable_sparse_encoding_) {
+    table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled;
+  }
+  if (force_sparse_encoding_) {
+    table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
+  }
 
   return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
                  xml_flattener_options_);
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 2cdb0c8..6c09649 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -34,10 +34,18 @@
     AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. "
             "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.",
         kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_);
-    AddOptionalSwitch("--enable-sparse-encoding",
+    AddOptionalSwitch(
+        "--enable-sparse-encoding",
         "Enables encoding sparse entries using a binary search tree.\n"
-        "This decreases APK size at the cost of resource retrieval performance.",
-         &table_flattener_options_.use_sparse_entries);
+        "This decreases APK size at the cost of resource retrieval performance.\n"
+        "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
+        "the APK is O+",
+        &enable_sparse_encoding_);
+    AddOptionalSwitch("--force-sparse-encoding",
+                      "Enables encoding sparse entries using a binary search tree.\n"
+                      "This decreases APK size at the cost of resource retrieval performance.\n"
+                      "Applies sparse encoding to all resources regardless of minSdk.",
+                      &force_sparse_encoding_);
     AddOptionalSwitch("--keep-raw-values",
         android::base::StringPrintf("Preserve raw attribute values in xml files when using the"
             " '%s' output format", kOutputFormatBinary),
@@ -56,6 +64,8 @@
   std::string output_path_;
   std::optional<std::string> output_format_;
   bool verbose_ = false;
+  bool enable_sparse_encoding_ = false;
+  bool force_sparse_encoding_ = false;
 };
 
 int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer,
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 74a8bbd..116dcd6 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2419,6 +2419,9 @@
                 << "the --merge-only flag can be only used when building a static library");
     return 1;
   }
+  if (options_.use_sparse_encoding) {
+    options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
+  }
 
   // The default build type.
   context.SetPackageType(PackageType::kApp);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index a5623cb..6c8d143 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -69,6 +69,7 @@
   bool no_resource_removal = false;
   bool no_xml_namespaces = false;
   bool do_not_compress_anything = false;
+  bool use_sparse_encoding = false;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
 
@@ -156,8 +157,8 @@
             "defaults. Use this only when building runtime resource overlay packages.",
         &options_.no_resource_removal);
     AddOptionalSwitch("--enable-sparse-encoding",
-        "This decreases APK size at the cost of resource retrieval performance.",
-        &options_.table_flattener_options.use_sparse_entries);
+                      "This decreases APK size at the cost of resource retrieval performance.",
+                      &options_.use_sparse_encoding);
     AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
         &legacy_x_flag_);
     AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 4033983..9feaf52 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -16,7 +16,11 @@
 
 #include "Optimize.h"
 
+#include <map>
 #include <memory>
+#include <set>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "Diagnostics.h"
@@ -38,9 +42,9 @@
 #include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "optimize/MultiApkGenerator.h"
+#include "optimize/Obfuscator.h"
 #include "optimize/ResourceDeduper.h"
 #include "optimize/ResourceFilter.h"
-#include "optimize/ResourcePathShortener.h"
 #include "optimize/VersionCollapser.h"
 #include "split/TableSplitter.h"
 #include "util/Files.h"
@@ -114,11 +118,11 @@
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
-
   StdErrDiagnostics diagnostics_;
   bool verbose_ = false;
   int sdk_version_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
 };
 
 class Optimizer {
@@ -151,8 +155,8 @@
     }
 
     if (options_.shorten_resource_paths) {
-      ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map);
-      if (!shortener.Consume(context_, apk->GetResourceTable())) {
+      Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+      if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
         context_->GetDiagnostics()->Error(android::DiagMessage()
                                           << "failed shortening resource paths");
         return 1;
@@ -427,6 +431,13 @@
     return 1;
   }
 
+  if (options_.enable_sparse_encoding) {
+    options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
+  }
+  if (options_.force_sparse_encoding) {
+    options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced;
+  }
+
   if (target_densities_) {
     // Parse the target screen densities.
     for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index ff63e8d..790bb74 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -26,8 +26,6 @@
 namespace aapt {
 
 struct OptimizeOptions {
-  friend class OptimizeCommand;
-
   // Path to the output APK.
   std::optional<std::string> output_path;
   // Path to the output APK directory for splits.
@@ -61,6 +59,12 @@
 
   // Path to the output map of original resource paths to shortened paths.
   std::optional<std::string> shortened_paths_map_path;
+
+  // Whether sparse encoding should be used for O+ resources.
+  bool enable_sparse_encoding = false;
+
+  // Whether sparse encoding should be used for all resources.
+  bool force_sparse_encoding = false;
 };
 
 class OptimizeCommand : public Command {
@@ -96,10 +100,18 @@
         "Comma separated list of artifacts to keep. If none are specified,\n"
             "all artifacts will be kept.",
         &kept_artifacts_);
-    AddOptionalSwitch("--enable-sparse-encoding",
+    AddOptionalSwitch(
+        "--enable-sparse-encoding",
         "Enables encoding sparse entries using a binary search tree.\n"
-            "This decreases APK size at the cost of resource retrieval performance.",
-        &options_.table_flattener_options.use_sparse_entries);
+        "This decreases APK size at the cost of resource retrieval performance.\n"
+        "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
+        "the APK is O+",
+        &options_.enable_sparse_encoding);
+    AddOptionalSwitch("--force-sparse-encoding",
+                      "Enables encoding sparse entries using a binary search tree.\n"
+                      "This decreases APK size at the cost of resource retrieval performance.\n"
+                      "Applies sparse encoding to all resources regardless of minSdk.",
+                      &options_.force_sparse_encoding);
     AddOptionalSwitch("--collapse-resource-names",
         "Collapses resource names to a single value in the key string pool. Resources can \n"
             "be exempted using the \"no_collapse\" directive in a file specified by "
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
new file mode 100644
index 0000000..8832c24
--- /dev/null
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "format/binary/ResEntryWriter.h"
+
+#include "ValueVisitor.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/Util.h"
+#include "format/binary/ResourceTypeExtensions.h"
+
+namespace aapt {
+
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
+struct less_style_entries {
+  bool operator()(const Style::Entry* a, const Style::Entry* b) const {
+    if (a->key.id) {
+      if (b->key.id) {
+        return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value());
+      }
+      return true;
+    }
+    if (!b->key.id) {
+      return a->key.name.value() < b->key.name.value();
+    }
+    return false;
+  }
+};
+
+class MapFlattenVisitor : public ConstValueVisitor {
+ public:
+  using ConstValueVisitor::Visit;
+
+  MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
+      : out_entry_(out_entry), buffer_(buffer) {
+  }
+
+  void Visit(const Attribute* attr) override {
+    {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
+      FlattenEntry(&key, &val);
+    }
+
+    if (attr->min_int != std::numeric_limits<int32_t>::min()) {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int));
+      FlattenEntry(&key, &val);
+    }
+
+    if (attr->max_int != std::numeric_limits<int32_t>::max()) {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int));
+      FlattenEntry(&key, &val);
+    }
+
+    for (const Attribute::Symbol& s : attr->symbols) {
+      BinaryPrimitive val(s.type, s.value);
+      FlattenEntry(&s.symbol, &val);
+    }
+  }
+
+  void Visit(const Style* style) override {
+    if (style->parent) {
+      const Reference& parent_ref = style->parent.value();
+      CHECK(bool(parent_ref.id)) << "parent has no ID";
+      out_entry_->parent.ident = android::util::HostToDevice32(parent_ref.id.value().id);
+    }
+
+    // Sort the style.
+    std::vector<const Style::Entry*> sorted_entries;
+    for (const auto& entry : style->entries) {
+      sorted_entries.emplace_back(&entry);
+    }
+
+    std::sort(sorted_entries.begin(), sorted_entries.end(), less_style_entries());
+
+    for (const Style::Entry* entry : sorted_entries) {
+      FlattenEntry(&entry->key, entry->value.get());
+    }
+  }
+
+  void Visit(const Styleable* styleable) override {
+    for (auto& attr_ref : styleable->entries) {
+      BinaryPrimitive val(Res_value{});
+      FlattenEntry(&attr_ref, &val);
+    }
+  }
+
+  void Visit(const Array* array) override {
+    const size_t count = array->elements.size();
+    for (size_t i = 0; i < count; i++) {
+      Reference key(android::ResTable_map::ATTR_MIN + i);
+      FlattenEntry(&key, array->elements[i].get());
+    }
+  }
+
+  void Visit(const Plural* plural) override {
+    const size_t count = plural->values.size();
+    for (size_t i = 0; i < count; i++) {
+      if (!plural->values[i]) {
+        continue;
+      }
+
+      ResourceId q;
+      switch (i) {
+        case Plural::Zero:
+          q.id = android::ResTable_map::ATTR_ZERO;
+          break;
+
+        case Plural::One:
+          q.id = android::ResTable_map::ATTR_ONE;
+          break;
+
+        case Plural::Two:
+          q.id = android::ResTable_map::ATTR_TWO;
+          break;
+
+        case Plural::Few:
+          q.id = android::ResTable_map::ATTR_FEW;
+          break;
+
+        case Plural::Many:
+          q.id = android::ResTable_map::ATTR_MANY;
+          break;
+
+        case Plural::Other:
+          q.id = android::ResTable_map::ATTR_OTHER;
+          break;
+
+        default:
+          LOG(FATAL) << "unhandled plural type";
+          break;
+      }
+
+      Reference key(q);
+      FlattenEntry(&key, plural->values[i].get());
+    }
+  }
+
+  /**
+   * Call this after visiting a Value. This will finish any work that
+   * needs to be done to prepare the entry.
+   */
+  void Finish() {
+    out_entry_->count = android::util::HostToDevice32(entry_count_);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
+
+  void FlattenKey(const Reference* key, ResTable_map* out_entry) {
+    CHECK(bool(key->id)) << "key has no ID";
+    out_entry->name.ident = android::util::HostToDevice32(key->id.value().id);
+  }
+
+  void FlattenValue(const Item* value, ResTable_map* out_entry) {
+    CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
+  }
+
+  void FlattenEntry(const Reference* key, Item* value) {
+    ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+    FlattenKey(key, out_entry);
+    FlattenValue(value, out_entry);
+    out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
+    entry_count_++;
+  }
+
+  ResTable_entry_ext* out_entry_;
+  BigBuffer* buffer_;
+  size_t entry_count_ = 0;
+};
+
+template <typename T>
+void WriteEntry(const FlatEntry* entry, T* out_result) {
+  static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
+                "T must be ResTable_entry or ResTable_entry_ext");
+
+  ResTable_entry* out_entry = (ResTable_entry*)out_result;
+  if (entry->entry->visibility.level == Visibility::Level::kPublic) {
+    out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+  }
+
+  if (entry->value->IsWeak()) {
+    out_entry->flags |= ResTable_entry::FLAG_WEAK;
+  }
+
+  if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
+    out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+  }
+
+  out_entry->flags = android::util::HostToDevice16(out_entry->flags);
+  out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
+  out_entry->size = android::util::HostToDevice16(sizeof(T));
+}
+
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
+  int32_t offset = buffer->size();
+  ResTable_entry_ext* out_entry = buffer->NextBlock<ResTable_entry_ext>();
+  WriteEntry<ResTable_entry_ext>(map_entry, out_entry);
+
+  MapFlattenVisitor visitor(out_entry, buffer);
+  map_entry->value->Accept(&visitor);
+  visitor.Finish();
+  return offset;
+}
+
+void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
+  static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+                "ResEntryValuePair must not have padding between entry and value.");
+
+  WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
+
+  CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
+  out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
+}
+
+int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
+  return WriteMapToBuffer(entry, entries_buffer_);
+}
+
+int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
+  int32_t offset = entries_buffer_->size();
+  auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
+  WriteItemToPair(entry, out_pair);
+  return offset;
+}
+
+std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
+  return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
+}
+
+bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
+                                                 const ResEntryValuePairRef& b) const {
+  return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
+}
+
+int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
+  return WriteMapToBuffer(entry, entries_buffer_);
+}
+
+int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
+  int32_t initial_offset = entries_buffer_->size();
+
+  auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
+  WriteItemToPair(entry, out_pair);
+
+  auto ref = ResEntryValuePairRef{*out_pair};
+  auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
+  if (inserted) {
+    // If inserted just return a new offset as this is a first time we store
+    // this entry.
+    return initial_offset;
+  }
+  // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+  // and return offset of previously stored entry.
+  entries_buffer_->BackUp(sizeof(ResEntryValuePair));
+  return it->second;
+}
+
+}  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
new file mode 100644
index 0000000..a36ceec
--- /dev/null
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 AAPT_FORMAT_BINARY_RESENTRY_SERIALIZER_H
+#define AAPT_FORMAT_BINARY_RESENTRY_SERIALIZER_H
+
+#include <unordered_map>
+
+#include "ResourceTable.h"
+#include "ValueVisitor.h"
+#include "android-base/macros.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/ResourceTypes.h"
+
+namespace aapt {
+
+struct FlatEntry {
+  const ResourceTableEntryView* entry;
+  const Value* value;
+
+  // The entry string pool index to the entry's name.
+  uint32_t entry_key;
+};
+
+// Pair of ResTable_entry and Res_value. These pairs are stored sequentially in values buffer.
+// We introduce this structure for ResEntryWriter to a have single allocation using
+// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
+struct ResEntryValuePair {
+  android::ResTable_entry entry;
+  android::Res_value value;
+};
+
+// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValuePair is stored.
+union ResEntryValuePairRef {
+  const std::reference_wrapper<const ResEntryValuePair> pair;
+  const u_char* ptr;
+
+  explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+  }
+};
+
+// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
+struct ResEntryValuePairContentHasher {
+  std::size_t operator()(const ResEntryValuePairRef& ref) const;
+};
+
+// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
+struct ResEntryValuePairContentEqualTo {
+  bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+};
+
+// Base class that allows to write FlatEntries into entries_buffer.
+class ResEntryWriter {
+ public:
+  virtual ~ResEntryWriter() = default;
+
+  // Writes resource table entry and its value into 'entries_buffer_' and returns offset
+  // in the buffer where entry was written.
+  int32_t Write(const FlatEntry* entry) {
+    if (ValueCast<Item>(entry->value) != nullptr) {
+      return WriteItem(entry);
+    } else {
+      return WriteMap(entry);
+    }
+  }
+
+ protected:
+  ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+  }
+  android::BigBuffer* entries_buffer_;
+
+  virtual int32_t WriteItem(const FlatEntry* entry) = 0;
+
+  virtual int32_t WriteMap(const FlatEntry* entry) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
+};
+
+// ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
+// Next entry is always written right after previous one in the buffer.
+class SequentialResEntryWriter : public ResEntryWriter {
+ public:
+  explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+      : ResEntryWriter(entries_buffer) {
+  }
+  ~SequentialResEntryWriter() override = default;
+
+  int32_t WriteItem(const FlatEntry* entry) override;
+
+  int32_t WriteMap(const FlatEntry* entry) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
+};
+
+// ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
+// Next entry is written into buffer only if there is no entry with the same bytes representation
+// in memory written before. Otherwise returns offset of already written entry.
+class DeduplicateItemsResEntryWriter : public ResEntryWriter {
+ public:
+  explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+      : ResEntryWriter(entries_buffer) {
+  }
+  ~DeduplicateItemsResEntryWriter() override = default;
+
+  int32_t WriteItem(const FlatEntry* entry) override;
+
+  int32_t WriteMap(const FlatEntry* entry) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
+
+  std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
+                     ResEntryValuePairContentEqualTo>
+      entry_offsets;
+};
+
+}  // namespace aapt
+
+#endif
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
new file mode 100644
index 0000000..56ca133
--- /dev/null
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "format/binary/ResEntryWriter.h"
+
+#include "androidfw/BigBuffer.h"
+#include "format/binary/ResourceTypeExtensions.h"
+#include "test/Test.h"
+#include "util/Util.h"
+
+using ::android::BigBuffer;
+using ::android::Res_value;
+using ::android::ResTable_map;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsNull;
+using ::testing::Ne;
+using ::testing::NotNull;
+
+namespace aapt {
+
+using SequentialResEntryWriterTest = CommandTestFixture;
+using DeduplicateItemsResEntryWriterTest = CommandTestFixture;
+
+std::vector<int32_t> WriteAllEntries(const ResourceTableView& table, ResEntryWriter& writer) {
+  std::vector<int32_t> result = {};
+  for (const auto& type : table.packages[0].types) {
+    for (const auto& entry : type.entries) {
+      for (const auto& value : entry.values) {
+        auto flat_entry = FlatEntry{&entry, value->value.get(), 0};
+        result.push_back(writer.Write(&flat_entry));
+      }
+    }
+  }
+  return result;
+}
+
+TEST_F(SequentialResEntryWriterTest, WriteEntriesOneByOne) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddSimple("com.app.test:id/id1", ResourceId(0x7f010000))
+          .AddSimple("com.app.test:id/id2", ResourceId(0x7f010001))
+          .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
+          .Build();
+
+  BigBuffer out(512);
+  SequentialResEntryWriter writer(&out);
+  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+  std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+                                        2 * sizeof(ResEntryValuePair)};
+  EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+  EXPECT_EQ(offsets, expected_offsets);
+};
+
+TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
+  std::unique_ptr<Array> array1 = util::make_unique<Array>();
+  array1->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
+  array1->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
+  std::unique_ptr<Array> array2 = util::make_unique<Array>();
+  array2->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
+  array2->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
+
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .AddValue("com.app.test:array/arr1", std::move(array1))
+                                             .AddValue("com.app.test:array/arr2", std::move(array2))
+                                             .Build();
+
+  BigBuffer out(512);
+  SequentialResEntryWriter writer(&out);
+  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+  std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+  EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+  EXPECT_EQ(offsets, expected_offsets);
+};
+
+TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddSimple("com.app.test:id/id1", ResourceId(0x7f010000))
+          .AddSimple("com.app.test:id/id2", ResourceId(0x7f010001))
+          .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
+          .Build();
+
+  BigBuffer out(512);
+  DeduplicateItemsResEntryWriter writer(&out);
+  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+  std::vector<int32_t> expected_offsets{0, 0, 0};
+  EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+  EXPECT_EQ(offsets, expected_offsets);
+};
+
+TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
+  std::unique_ptr<Array> array1 = util::make_unique<Array>();
+  array1->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
+  array1->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
+  std::unique_ptr<Array> array2 = util::make_unique<Array>();
+  array2->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
+  array2->elements.push_back(
+      util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
+
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .AddValue("com.app.test:array/arr1", std::move(array1))
+                                             .AddValue("com.app.test:array/arr2", std::move(array2))
+                                             .Build();
+
+  BigBuffer out(512);
+  DeduplicateItemsResEntryWriter writer(&out);
+  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+  std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+  EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+  EXPECT_EQ(offsets, expected_offsets);
+};
+
+}  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 4fb7ed1..7dc9d26 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -16,21 +16,20 @@
 
 #include "format/binary/TableFlattener.h"
 
-#include <algorithm>
-#include <numeric>
 #include <sstream>
 #include <type_traits>
+#include <variant>
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "SdkConstants.h"
-#include "ValueVisitor.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/BigBuffer.h"
 #include "androidfw/ResourceUtils.h"
 #include "format/binary/ChunkWriter.h"
+#include "format/binary/ResEntryWriter.h"
 #include "format/binary/ResourceTypeExtensions.h"
 #include "trace/TraceBuffer.h"
 
@@ -58,170 +57,6 @@
   dst[i] = 0;
 }
 
-static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) {
-  if (a->key.id) {
-    if (b->key.id) {
-      return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value());
-    }
-    return true;
-  } else if (!b->key.id) {
-    return a->key.name.value() < b->key.name.value();
-  }
-  return false;
-}
-
-struct FlatEntry {
-  const ResourceTableEntryView* entry;
-  const Value* value;
-
-  // The entry string pool index to the entry's name.
-  uint32_t entry_key;
-};
-
-class MapFlattenVisitor : public ConstValueVisitor {
- public:
-  using ConstValueVisitor::Visit;
-
-  MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
-      : out_entry_(out_entry), buffer_(buffer) {
-  }
-
-  void Visit(const Attribute* attr) override {
-    {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
-      FlattenEntry(&key, &val);
-    }
-
-    if (attr->min_int != std::numeric_limits<int32_t>::min()) {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int));
-      FlattenEntry(&key, &val);
-    }
-
-    if (attr->max_int != std::numeric_limits<int32_t>::max()) {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int));
-      FlattenEntry(&key, &val);
-    }
-
-    for (const Attribute::Symbol& s : attr->symbols) {
-      BinaryPrimitive val(s.type, s.value);
-      FlattenEntry(&s.symbol, &val);
-    }
-  }
-
-  void Visit(const Style* style) override {
-    if (style->parent) {
-      const Reference& parent_ref = style->parent.value();
-      CHECK(bool(parent_ref.id)) << "parent has no ID";
-      out_entry_->parent.ident = android::util::HostToDevice32(parent_ref.id.value().id);
-    }
-
-    // Sort the style.
-    std::vector<const Style::Entry*> sorted_entries;
-    for (const auto& entry : style->entries) {
-      sorted_entries.emplace_back(&entry);
-    }
-
-    std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_style_entries);
-
-    for (const Style::Entry* entry : sorted_entries) {
-      FlattenEntry(&entry->key, entry->value.get());
-    }
-  }
-
-  void Visit(const Styleable* styleable) override {
-    for (auto& attr_ref : styleable->entries) {
-      BinaryPrimitive val(Res_value{});
-      FlattenEntry(&attr_ref, &val);
-    }
-  }
-
-  void Visit(const Array* array) override {
-    const size_t count = array->elements.size();
-    for (size_t i = 0; i < count; i++) {
-      Reference key(android::ResTable_map::ATTR_MIN + i);
-      FlattenEntry(&key, array->elements[i].get());
-    }
-  }
-
-  void Visit(const Plural* plural) override {
-    const size_t count = plural->values.size();
-    for (size_t i = 0; i < count; i++) {
-      if (!plural->values[i]) {
-        continue;
-      }
-
-      ResourceId q;
-      switch (i) {
-        case Plural::Zero:
-          q.id = android::ResTable_map::ATTR_ZERO;
-          break;
-
-        case Plural::One:
-          q.id = android::ResTable_map::ATTR_ONE;
-          break;
-
-        case Plural::Two:
-          q.id = android::ResTable_map::ATTR_TWO;
-          break;
-
-        case Plural::Few:
-          q.id = android::ResTable_map::ATTR_FEW;
-          break;
-
-        case Plural::Many:
-          q.id = android::ResTable_map::ATTR_MANY;
-          break;
-
-        case Plural::Other:
-          q.id = android::ResTable_map::ATTR_OTHER;
-          break;
-
-        default:
-          LOG(FATAL) << "unhandled plural type";
-          break;
-      }
-
-      Reference key(q);
-      FlattenEntry(&key, plural->values[i].get());
-    }
-  }
-
-  /**
-   * Call this after visiting a Value. This will finish any work that
-   * needs to be done to prepare the entry.
-   */
-  void Finish() {
-    out_entry_->count = android::util::HostToDevice32(entry_count_);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
-
-  void FlattenKey(const Reference* key, ResTable_map* out_entry) {
-    CHECK(bool(key->id)) << "key has no ID";
-    out_entry->name.ident = android::util::HostToDevice32(key->id.value().id);
-  }
-
-  void FlattenValue(const Item* value, ResTable_map* out_entry) {
-    CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
-  }
-
-  void FlattenEntry(const Reference* key, Item* value) {
-    ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
-    FlattenKey(key, out_entry);
-    FlattenValue(value, out_entry);
-    out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
-    entry_count_++;
-  }
-
-  ResTable_entry_ext* out_entry_;
-  BigBuffer* buffer_;
-  size_t entry_count_ = 0;
-};
-
 struct OverlayableChunk {
   std::string actor;
   android::Source source;
@@ -231,16 +66,18 @@
 class PackageFlattener {
  public:
   PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
-                   const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
-                   bool collapse_key_stringpool,
-                   const std::set<ResourceName>& name_collapse_exemptions)
+                   const std::map<size_t, std::string>* shared_libs,
+                   SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+                   const std::set<ResourceName>& name_collapse_exemptions,
+                   bool deduplicate_entry_values)
       : context_(context),
         diag_(context->GetDiagnostics()),
         package_(package),
         shared_libs_(shared_libs),
-        use_sparse_entries_(use_sparse_entries),
+        sparse_entries_(sparse_entries),
         collapse_key_stringpool_(collapse_key_stringpool),
-        name_collapse_exemptions_(name_collapse_exemptions) {
+        name_collapse_exemptions_(name_collapse_exemptions),
+        deduplicate_entry_values_(deduplicate_entry_values) {
   }
 
   bool FlattenPackage(BigBuffer* buffer) {
@@ -298,47 +135,6 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
 
-  template <typename T, bool IsItem>
-  T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) {
-    static_assert(
-        std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value,
-        "T must be ResTable_entry or ResTable_entry_ext");
-
-    T* result = buffer->NextBlock<T>();
-    ResTable_entry* out_entry = (ResTable_entry*)result;
-    if (entry->entry->visibility.level == Visibility::Level::kPublic) {
-      out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
-    }
-
-    if (entry->value->IsWeak()) {
-      out_entry->flags |= ResTable_entry::FLAG_WEAK;
-    }
-
-    if (!IsItem) {
-      out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
-    }
-
-    out_entry->flags = android::util::HostToDevice16(out_entry->flags);
-    out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
-    out_entry->size = android::util::HostToDevice16(sizeof(T));
-    return result;
-  }
-
-  bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) {
-    if (const Item* item = ValueCast<Item>(entry->value)) {
-      WriteEntry<ResTable_entry, true>(entry, buffer);
-      Res_value* outValue = buffer->NextBlock<Res_value>();
-      CHECK(item->Flatten(outValue)) << "flatten failed";
-      outValue->size = android::util::HostToDevice16(sizeof(*outValue));
-    } else {
-      ResTable_entry_ext* out_entry = WriteEntry<ResTable_entry_ext, false>(entry, buffer);
-      MapFlattenVisitor visitor(out_entry, buffer);
-      entry->value->Accept(&visitor);
-      visitor.Finish();
-    }
-    return true;
-  }
-
   bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
                      const size_t num_total_entries, std::vector<FlatEntry>* entries,
                      BigBuffer* buffer) {
@@ -355,22 +151,26 @@
     offsets.resize(num_total_entries, 0xffffffffu);
 
     android::BigBuffer values_buffer(512);
-    for (FlatEntry& flat_entry : *entries) {
-      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
-      offsets[flat_entry.entry->id.value()] = values_buffer.size();
-      if (!FlattenValue(&flat_entry, &values_buffer)) {
-        diag_->Error(android::DiagMessage()
-                     << "failed to flatten resource '"
-                     << ResourceNameRef(package_.name, type.named_type, flat_entry.entry->name)
-                     << "' for configuration '" << config << "'");
-        return false;
-      }
+    std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
+        writer_variant;
+    ResEntryWriter* res_entry_writer;
+    if (deduplicate_entry_values_) {
+      res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
+    } else {
+      res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
     }
 
-    bool sparse_encode = use_sparse_entries_;
+    for (FlatEntry& flat_entry : *entries) {
+      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
+      offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry);
+    }
 
-    if (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0) {
-      // Sparse encode if sdk version is not set in context and config.
+    bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
+                         sparse_entries_ == SparseEntriesMode::Forced;
+
+    if (sparse_entries_ == SparseEntriesMode::Forced ||
+        (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
+      // Sparse encode if forced or sdk version is not set in context and config.
     } else {
       // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
       sparse_encode = sparse_encode &&
@@ -712,12 +512,13 @@
   android::IDiagnostics* diag_;
   const ResourceTablePackageView package_;
   const std::map<size_t, std::string>* shared_libs_;
-  bool use_sparse_entries_;
+  SparseEntriesMode sparse_entries_;
   android::StringPool type_pool_;
   android::StringPool key_pool_;
   bool collapse_key_stringpool_;
   const std::set<ResourceName>& name_collapse_exemptions_;
   std::map<uint32_t, uint32_t> aliases_;
+  bool deduplicate_entry_values_;
 };
 
 }  // namespace
@@ -768,8 +569,9 @@
     }
 
     PackageFlattener flattener(context, package, &table->included_packages_,
-                               options_.use_sparse_entries, options_.collapse_key_stringpool,
-                               options_.name_collapse_exemptions);
+                               options_.sparse_entries, options_.collapse_key_stringpool,
+                               options_.name_collapse_exemptions,
+                               options_.deduplicate_entry_values);
     if (!flattener.FlattenPackage(&package_buffer)) {
       return false;
     }
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 1eec0e4..6151b7e 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -29,12 +29,20 @@
 // preferred.
 constexpr const size_t kSparseEncodingThreshold = 60;
 
+enum class SparseEntriesMode {
+  // Disables sparse encoding for entries.
+  Disabled,
+  // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less
+  // than O only applies sparse encoding for resource configuration available on O+.
+  Enabled,
+  // Enables sparse encoding for all entries regardless of minSdk.
+  Forced,
+};
+
 struct TableFlattenerOptions {
-  // When true, types for configurations with a sparse set of entries are encoded
+  // When enabled, types for configurations with a sparse set of entries are encoded
   // as a sparse map of entry ID and offset to actual data.
-  // This is only available on platforms O+ and will only be respected when
-  // minSdk is O+.
-  bool use_sparse_entries = false;
+  SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
 
   // When true, the key string pool in the final ResTable
   // is collapsed to a single entry. All resource entries
@@ -46,6 +54,20 @@
 
   // Map from original resource paths to shortened resource paths.
   std::map<std::string, std::string> shortened_path_map;
+
+  // When enabled, only unique pairs of entry and value are stored in type chunks.
+  //
+  // By default, all such pairs are unique because a reference to resource name in the string pool
+  // is a part of the pair. But when resource names are collapsed (using 'collapse_key_stringpool'
+  // flag or manually) the same data might be duplicated multiple times in the same type chunk.
+  //
+  // For example: an application has 3 boolean resources with collapsed names and 3 'true' values
+  // are defined for these resources in 'default' configuration. All pairs of entry and value for
+  // these resources will have the same binary representation and stored only once in type chunk
+  // instead of three times when this flag is disabled.
+  //
+  // This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
+  bool deduplicate_entry_values = false;
 };
 
 class TableFlattener : public IResourceTableConsumer {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f551bf6..2097a63 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -337,7 +337,7 @@
   auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
 
   TableFlattenerOptions options;
-  options.use_sparse_entries = true;
+  options.sparse_entries = SparseEntriesMode::Enabled;
 
   std::string no_sparse_contents;
   ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
@@ -380,7 +380,29 @@
   auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
 
   TableFlattenerOptions options;
-  options.use_sparse_entries = true;
+  options.sparse_entries = SparseEntriesMode::Enabled;
+
+  std::string no_sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+  std::string sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+                                              .SetCompilationPackage("android")
+                                              .SetPackageId(0x01)
+                                              .SetMinSdkVersion(SDK_LOLLIPOP)
+                                              .Build();
+
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+  TableFlattenerOptions options;
+  options.sparse_entries = SparseEntriesMode::Forced;
 
   std::string no_sparse_contents;
   ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
@@ -399,7 +421,7 @@
   auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
 
   TableFlattenerOptions options;
-  options.use_sparse_entries = true;
+  options.sparse_entries = SparseEntriesMode::Enabled;
 
   std::string no_sparse_contents;
   ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
@@ -442,7 +464,7 @@
   auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
 
   TableFlattenerOptions options;
-  options.use_sparse_entries = true;
+  options.sparse_entries = SparseEntriesMode::Enabled;
 
   std::string no_sparse_contents;
   ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
@@ -647,6 +669,87 @@
                      ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
 }
 
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithDeduplicationSucceeds) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
+          .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
+          .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
+                    test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
+          .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
+                    util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+          .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+                    ResourceId(0x7f030000),
+                    util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+          .AddString("com.app.test:string/test1", ResourceId(0x7f040000), "foo")
+          .AddString("com.app.test:string/test2", ResourceId(0x7f040001), "foo")
+          .AddString("com.app.test:string/test3", ResourceId(0x7f040002), "bar")
+          .AddString("com.app.test:string/test4", ResourceId(0x7f040003), "foo")
+          .AddString("com.app.test:layout/bar1", ResourceId(0x7f050000), "res/layout/bar.xml")
+          .AddString("com.app.test:layout/bar2", ResourceId(0x7f050001), "res/layout/bar.xml")
+          .Build();
+
+  TableFlattenerOptions options;
+  options.collapse_key_stringpool = true;
+  options.deduplicate_entry_values = true;
+
+  ResTable res_table;
+
+  ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+                     ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+                     ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+                     ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+                     ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
+                     ResTable_config::CONFIG_VERSION));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+                     ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
+                     2u, ResTable_config::CONFIG_VERSION));
+
+  std::u16string foo_str = u"foo";
+  std::u16string bar_str = u"bar";
+  auto foo_idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
+  auto bar_idx = res_table.getTableStringBlock(0)->indexOfString(bar_str.data(), bar_str.size());
+  ASSERT_TRUE(foo_idx.has_value());
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
+                     ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
+                     ResourceId(0x7f040001), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
+                     ResourceId(0x7f040002), {}, Res_value::TYPE_STRING, (uint32_t)*bar_idx, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
+                     ResourceId(0x7f040003), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
+
+  std::u16string bar_path = u"res/layout/bar.xml";
+  auto bar_path_idx =
+      res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
+  ASSERT_TRUE(bar_path_idx.has_value());
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
+                     ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx,
+                     0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
+                     ResourceId(0x7f050001), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx,
+                     0u));
+
+  std::string deduplicated_output;
+  std::string sequential_output;
+  Flatten(context_.get(), options, table.get(), &deduplicated_output);
+  options.deduplicate_entry_values = false;
+  Flatten(context_.get(), options, table.get(), &sequential_output);
+
+  // We have 4 duplicates: 0x7f020001 id, 0x7f040001 string, 0x7f040003 string, 0x7f050001 layout.
+  EXPECT_EQ(sequential_output.size(),
+            deduplicated_output.size() + 4 * (sizeof(ResTable_entry) + sizeof(Res_value)));
+}
+
 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/Obfuscator.cpp
similarity index 83%
rename from tools/aapt2/optimize/ResourcePathShortener.cpp
rename to tools/aapt2/optimize/Obfuscator.cpp
index 7ff9bf5..f704f26 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -14,28 +14,25 @@
  * limitations under the License.
  */
 
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
 
 #include <set>
+#include <string>
 #include <unordered_set>
 
-#include "androidfw/StringPiece.h"
-
 #include "ResourceTable.h"
 #include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
 #include "util/Util.h"
 
-
-static const std::string base64_chars =
-             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-             "abcdefghijklmnopqrstuvwxyz"
-             "0123456789-_";
+static const char base64_chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    "abcdefghijklmnopqrstuvwxyz"
+    "0123456789-_";
 
 namespace aapt {
 
-ResourcePathShortener::ResourcePathShortener(
-    std::map<std::string, std::string>& path_map_out)
-    : path_map_(path_map_out) {
+Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
 }
 
 std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
@@ -50,7 +47,6 @@
   return result;
 }
 
-
 // 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/
@@ -63,7 +59,7 @@
 }
 
 std::string GetShortenedPath(const android::StringPiece& shortened_filename,
-    const android::StringPiece& extension, int collision_count) {
+                             const android::StringPiece& extension, int collision_count) {
   std::string shortened_path = "res/" + shortened_filename.to_string();
   if (collision_count > 0) {
     shortened_path += std::to_string(collision_count);
@@ -76,12 +72,12 @@
 // underlying filepath as key rather than the integer address. This is to ensure
 // determinism of output for colliding files.
 struct PathComparator {
-    bool operator() (const FileReference* lhs, const FileReference* rhs) const {
-        return lhs->path->compare(*rhs->path);
-    }
+  bool operator()(const FileReference* lhs, const FileReference* rhs) const {
+    return lhs->path->compare(*rhs->path);
+  }
 };
 
-bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) {
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
   // used to detect collisions
   std::unordered_set<std::string> shortened_paths;
   std::set<FileReference*, PathComparator> file_refs;
@@ -103,8 +99,7 @@
     util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
 
     // Android detects ColorStateLists via pathname, skip res/color*
-    if (util::StartsWith(res_subdir, "res/color"))
-      continue;
+    if (util::StartsWith(res_subdir, "res/color")) continue;
 
     std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
     int collision_count = 0;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/Obfuscator.h
similarity index 72%
rename from tools/aapt2/optimize/ResourcePathShortener.h
rename to tools/aapt2/optimize/Obfuscator.h
index f1074ef..1ea32db 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
-#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
+#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
 
 #include <map>
+#include <string>
 
 #include "android-base/macros.h"
-
 #include "process/IResourceTableConsumer.h"
 
 namespace aapt {
@@ -28,17 +28,17 @@
 class ResourceTable;
 
 // Maps resources in the apk to shortened paths.
-class ResourcePathShortener : public IResourceTableConsumer {
+class Obfuscator : public IResourceTableConsumer {
  public:
-  explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out);
+  explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
 
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener);
   std::map<std::string, std::string>& path_map_;
+  DISALLOW_COPY_AND_ASSIGN(Obfuscator);
 };
 
-} // namespace aapt
+}  // namespace aapt
 
-#endif  // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#endif  // TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
similarity index 80%
rename from tools/aapt2/optimize/ResourcePathShortener_test.cpp
rename to tools/aapt2/optimize/Obfuscator_test.cpp
index f5a02be..a3339d4 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
+
+#include <memory>
+#include <string>
 
 #include "ResourceTable.h"
 #include "test/Test.h"
 
 using ::aapt::test::GetValue;
+using ::testing::Eq;
 using ::testing::Not;
 using ::testing::NotNull;
-using ::testing::Eq;
 
 android::StringPiece GetExtension(android::StringPiece path) {
   auto iter = std::find(path.begin(), path.end(), '.');
@@ -30,16 +33,15 @@
 }
 
 void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
-  for (int i=start; i<end; i++) {
-    builder.AddFileReference(
-        "android:drawable/xmlfile" + std::to_string(i),
-        "res/drawable/xmlfile" + std::to_string(i) + ".xml");
+  for (int i = start; i < end; i++) {
+    builder.AddFileReference("android:drawable/xmlfile" + std::to_string(i),
+                             "res/drawable/xmlfile" + std::to_string(i) + ".xml");
   }
 }
 
 namespace aapt {
 
-TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
+TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
   std::unique_ptr<ResourceTable> table =
@@ -50,7 +52,7 @@
           .Build();
 
   std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -64,39 +66,36 @@
   EXPECT_THAT(path_map["res/drawables/xmlfile.xml"],
               Not(Eq(path_map["res/drawables/xmlfile2.xml"])));
 
-  FileReference* ref =
-      GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
+  FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
   ASSERT_THAT(ref, NotNull());
   // The map correctly points to the new location of the file
   EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path));
 
   // Strings should not be affected, only file paths
-  EXPECT_THAT(
-      *GetValue<String>(table.get(), "android:string/string")->value,
+  EXPECT_THAT(*GetValue<String>(table.get(), "android:string/string")->value,
               Eq("res/should/still/be/the/same.png"));
   EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
 }
 
-TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
+TEST(ObfuscatorTest, SkipColorFileRefPaths) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
-          .AddFileReference("android:color/colorlist",
-                            "res/color-mdp-v21/colorlist.xml",
+          .AddFileReference("android:color/colorlist", "res/color-mdp-v21/colorlist.xml",
                             test::ParseConfigOrDie("mdp-v21"))
           .Build();
 
   std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
 
   // Expect that the path map to not contain the ColorStateList
   ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
   ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
 }
 
-TEST(ResourcePathShortenerTest, KeepExtensions) {
+TEST(ObfuscatorTest, KeepExtensions) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
   std::string original_xml_path = "res/drawable/xmlfile.xml";
@@ -109,7 +108,7 @@
           .Build();
 
   std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -122,7 +121,7 @@
   EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
 }
 
-TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
+TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
   // 4000 resources is the limit at which the hash space is expanded to 3
@@ -135,27 +134,27 @@
   FillTable(builder1, 0, kNumResources);
   std::unique_ptr<ResourceTable> table1 = builder1.Build();
   std::map<std::string, std::string> expected_mapping;
-  ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get()));
+  ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
 
   // We are trying to ensure lack of non-determinism, it is not simple to prove
   // a negative, thus we must try the test a few times so that the test itself
   // is non-flaky. Basically create the pathmap 5 times from the same set of
   // resources but a different order of addition and then ensure they are always
   // mapped to the same short path.
-  for (int i=0; i<kNumTries; i++) {
+  for (int i = 0; i < kNumTries; i++) {
     test::ResourceTableBuilder builder2;
     // This loop adds resources to the resource table in the range of
     // [0:kNumResources).  Adding the file references in different order makes
     // non-determinism more likely to surface. Thus we add resources
     // [start_index:kNumResources) first then [0:start_index). We also use a
     // different start_index each run.
-    int start_index = (kNumResources/kNumTries)*i;
+    int start_index = (kNumResources / kNumTries) * i;
     FillTable(builder2, start_index, kNumResources);
     FillTable(builder2, 0, start_index);
     std::unique_ptr<ResourceTable> table2 = builder2.Build();
 
     std::map<std::string, std::string> actual_mapping;
-    ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get()));
+    ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
 
     for (auto& item : actual_mapping) {
       ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -163,4 +162,4 @@
   }
 }
 
-}   // namespace aapt
+}  // namespace aapt
diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS
index 2c526a1..33e237d 100644
--- a/tools/lint/OWNERS
+++ b/tools/lint/OWNERS
@@ -4,3 +4,6 @@
 per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS
 per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com
 
+# Android lint in the Android platform maintainers
+colefaust@google.com
+farivar@google.com
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 8aa3e25..4d69d26 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -42,6 +42,8 @@
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
         RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
+        PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
+        PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
     )
 
     override val api: Int
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
index 82eb8ed..3d5d01c 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
@@ -34,3 +34,7 @@
         Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
         Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
 )
+
+const val ANNOTATION_PERMISSION_METHOD = "android.content.pm.PermissionMethod"
+const val ANNOTATION_PERMISSION_NAME = "android.content.pm.PermissionName"
+const val ANNOTATION_PERMISSION_RESULT = "android.content.pm.PackageManager.PermissionResult"
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
new file mode 100644
index 0000000..68a450d
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.getUMethod
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.UReturnExpression
+import org.jetbrains.uast.getContainingUMethod
+
+/**
+ * Stops incorrect usage of {@link PermissionMethod}
+ * TODO: add tests once re-enabled (b/240445172, b/247542171)
+ */
+class PermissionMethodDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> =
+        listOf(UAnnotation::class.java, UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler =
+        PermissionMethodHandler(context)
+
+    private inner class PermissionMethodHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (hasPermissionMethodAnnotation(node)) return
+            if (onlyCallsPermissionMethod(node)) {
+                val location = context.getLocation(node.javaPsi.modifierList)
+                val fix = fix()
+                    .annotate(ANNOTATION_PERMISSION_METHOD)
+                    .range(location)
+                    .autoFix()
+                    .build()
+
+                context.report(
+                    ISSUE_CAN_BE_PERMISSION_METHOD,
+                    location,
+                    "Annotate method with @PermissionMethod",
+                    fix
+                )
+            }
+        }
+
+        override fun visitAnnotation(node: UAnnotation) {
+            if (node.qualifiedName != ANNOTATION_PERMISSION_METHOD) return
+            val method = node.getContainingUMethod() ?: return
+
+            if (!isPermissionMethodReturnType(method)) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` should return `void`, \
+                            `boolean`, or `@PackageManager.PermissionResult int`."
+                    """.trimIndent()
+                )
+            }
+
+            if (method.returnType == PsiType.INT &&
+                method.annotations.none { it.hasQualifiedName(ANNOTATION_PERMISSION_RESULT) }
+            ) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` that return `int` should \
+                            also be annotated with `@PackageManager.PermissionResult.`"
+                    """.trimIndent()
+                )
+            }
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION_PERMISSION_METHOD_USAGE = """
+            `@PermissionMethod` should annotate methods that ONLY perform permission lookups. \
+            Said methods should return `boolean`, `@PackageManager.PermissionResult int`, or return \
+            `void` and potentially throw `SecurityException`.
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_PERMISSION_METHOD_USAGE = Issue.create(
+            id = "PermissionMethodUsage",
+            briefDescription = "@PermissionMethod used incorrectly",
+            explanation = EXPLANATION_PERMISSION_METHOD_USAGE,
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = true
+        )
+
+        private val EXPLANATION_CAN_BE_PERMISSION_METHOD = """
+            Methods that only call other methods annotated with @PermissionMethod (and do NOTHING else) can themselves \
+            be annotated with @PermissionMethod.  For example:
+            ```
+            void wrapperHelper() {
+              // Context.enforceCallingPermission is annotated with @PermissionMethod
+              context.enforceCallingPermission(SOME_PERMISSION)
+            }
+            ```
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_CAN_BE_PERMISSION_METHOD = Issue.create(
+            id = "CanBePermissionMethod",
+            briefDescription = "Method can be annotated with @PermissionMethod",
+            explanation = EXPLANATION_CAN_BE_PERMISSION_METHOD,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false
+        )
+
+        private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+            .any {
+                it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
+            }
+
+        private fun isPermissionMethodReturnType(method: UMethod): Boolean =
+            listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
+
+        /**
+         * Identifies methods that...
+         * DO call other methods annotated with @PermissionMethod
+         * DO NOT do anything else
+         */
+        private fun onlyCallsPermissionMethod(method: UMethod): Boolean {
+            val body = method.uastBody as? UBlockExpression ?: return false
+            if (body.expressions.isEmpty()) return false
+            for (expression in body.expressions) {
+                when (expression) {
+                    is UQualifiedReferenceExpression -> {
+                        if (!isPermissionMethodCall(expression.selector)) return false
+                    }
+                    is UReturnExpression -> {
+                        if (!isPermissionMethodCall(expression.returnExpression)) return false
+                    }
+                    is UCallExpression -> {
+                        if (!isPermissionMethodCall(expression)) return false
+                    }
+                    is UIfExpression -> {
+                        if (expression.thenExpression !is UReturnExpression) return false
+                        if (!isPermissionMethodCall(expression.condition)) return false
+                    }
+                    else -> return false
+                }
+            }
+            return true
+        }
+
+        private fun isPermissionMethodCall(expression: UExpression?): Boolean {
+            return when (expression) {
+                is UQualifiedReferenceExpression ->
+                    return isPermissionMethodCall(expression.selector)
+                is UCallExpression -> {
+                    val calledMethod = expression.resolve()?.getUMethod() ?: return false
+                    return hasPermissionMethodAnnotation(calledMethod)
+                }
+                else -> false
+            }
+        }
+    }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index a415217..bba819c 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -82,8 +82,16 @@
             if (attr1[i].name != attr2[i].name) {
                 return false
             }
-            val v1 = ConstantEvaluator.evaluate(context, attr1[i].value)
-            val v2 = ConstantEvaluator.evaluate(context, attr2[i].value)
+            val value1 = attr1[i].value
+            val value2 = attr2[i].value
+            if (value1 == null && value2 == null) {
+                continue
+            }
+            if (value1 == null || value2 == null) {
+                return false
+            }
+            val v1 = ConstantEvaluator.evaluate(context, value1)
+            val v2 = ConstantEvaluator.evaluate(context, value2)
             if (v1 != v2) {
                 return false
             }
diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java
index a71b6a8..3f6658a 100644
--- a/tools/preload/loadclass/LoadClass.java
+++ b/tools/preload/loadclass/LoadClass.java
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-import android.util.Log;
 import android.os.Debug;
+import android.util.Log;
 
 /**
  * Loads a class, runs the garbage collector, and prints showmap output.
@@ -28,7 +28,7 @@
         System.loadLibrary("android_runtime");
 
         if (registerNatives() < 0) {
-            throw new RuntimeException("Error registering natives.");    
+            throw new RuntimeException("Error registering natives.");
         }
 
         Debug.startAllocCounting();
@@ -46,7 +46,7 @@
             }
         }
 
-        System.gc();
+        Runtime.getRuntime().gc();
 
         int allocCount = Debug.getGlobalAllocCount();
         int allocSize = Debug.getGlobalAllocSize();
@@ -73,7 +73,7 @@
         response.append(',').append(freedCount);
         response.append(',').append(freedSize);
         response.append(',').append(nativeHeapSize);
-        
+
         System.out.println(response.toString());
     }
 
diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp
index 985874b..fe97a90 100644
--- a/tools/processors/immutability/Android.bp
+++ b/tools/processors/immutability/Android.bp
@@ -14,24 +14,26 @@
         "src/**/*.java",
     ],
     use_tools_jar: true,
-    // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java
-    // language level 8 (see b/139342589):
-    java_version: "1.8",
-    openjdk9: {
-        javacflags: [
-            "--add-modules=jdk.compiler",
-            "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
-        ],
-    },
+    javacflags: [
+        "--add-modules=jdk.compiler",
+        "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
 }
 
 java_plugin {
     name: "ImmutabilityAnnotationProcessor",
     processor_class: "android.processor.immutability.ImmutabilityProcessor",
     static_libs: ["ImmutabilityAnnotationProcessorHostLibrary"],
+    javacflags: [
+        "--add-modules=jdk.compiler",
+        "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
 }
 
 java_library {
@@ -58,6 +60,13 @@
     java_resources: [":ImmutabilityAnnotationJavaSource"],
 
     test_suites: ["general-tests"],
+    javacflags: [
+        "--add-modules=jdk.compiler",
+        "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
 }
 
 filegroup {
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index 43fc3a7..f29d9b2 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
+
 package android.processor.immutability
 
 import com.sun.tools.javac.code.Symbol
@@ -35,10 +37,11 @@
 class ImmutabilityProcessor : AbstractProcessor() {
 
     companion object {
+
         /**
-         * Types that are already immutable.
+         * Types that are already immutable. Will also ignore subclasses.
          */
-        private val IGNORED_TYPES = listOf(
+        private val IGNORED_SUPER_TYPES = listOf(
             "java.io.File",
             "java.lang.Boolean",
             "java.lang.Byte",
@@ -54,6 +57,15 @@
             "android.os.Parcelable.Creator",
         )
 
+        /**
+         * Types that are already immutable. Must be an exact match, does not include any super
+         * or sub classes.
+         */
+        private val IGNORED_EXACT_TYPES = listOf(
+            "java.lang.Class",
+            "java.lang.Object",
+        )
+
         private val IGNORED_METHODS = listOf(
             "writeToParcel",
         )
@@ -62,7 +74,8 @@
     private lateinit var collectionType: TypeMirror
     private lateinit var mapType: TypeMirror
 
-    private lateinit var ignoredTypes: List<TypeMirror>
+    private lateinit var ignoredSuperTypes: List<TypeMirror>
+    private lateinit var ignoredExactTypes: List<TypeMirror>
 
     private val seenTypesByPolicy = mutableMapOf<Set<Immutable.Policy.Exception>, Set<Type>>()
 
@@ -74,7 +87,8 @@
         super.init(processingEnv)
         collectionType = processingEnv.erasedType("java.util.Collection")!!
         mapType = processingEnv.erasedType("java.util.Map")!!
-        ignoredTypes = IGNORED_TYPES.mapNotNull { processingEnv.erasedType(it) }
+        ignoredSuperTypes = IGNORED_SUPER_TYPES.mapNotNull { processingEnv.erasedType(it) }
+        ignoredExactTypes = IGNORED_EXACT_TYPES.mapNotNull { processingEnv.erasedType(it) }
     }
 
     override fun process(
@@ -107,7 +121,7 @@
         classType: Symbol.TypeSymbol,
         parentPolicyExceptions: Set<Immutable.Policy.Exception>,
     ): Boolean {
-        if (classType.getAnnotation(Immutable.Ignore::class.java) != null) return false
+        if (isIgnored(classType)) return false
 
         val policyAnnotation = classType.getAnnotation(Immutable.Policy::class.java)
         val newPolicyExceptions = parentPolicyExceptions + policyAnnotation?.exceptions.orEmpty()
@@ -129,7 +143,7 @@
             .fold(false) { anyError, field ->
                 if (field.isStatic) {
                     if (!field.isPrivate) {
-                        var finalityError = !field.modifiers.contains(Modifier.FINAL)
+                        val finalityError = !field.modifiers.contains(Modifier.FINAL)
                         if (finalityError) {
                             printError(parentChain, field, MessageUtils.staticNonFinalFailure())
                         }
@@ -175,8 +189,10 @@
         val newChain = parentChain + "$classType"
 
         val hasMethodError = filteredElements
+            .asSequence()
             .filter { it.getKind() == ElementKind.METHOD }
             .map { it as Symbol.MethodSymbol }
+            .filterNot { it.isStatic }
             .filterNot { IGNORED_METHODS.contains(it.name.toString()) }
             .fold(false) { anyError, method ->
                 // Must call visitMethod first so it doesn't get short circuited by the ||
@@ -206,8 +222,16 @@
             }
         }
 
-        if (isRegularClass && !anyError && allowFinalClassesFinalFields
-            && !classType.modifiers.contains(Modifier.FINAL)
+        // Check all of the super classes, since methods in those classes are also accessible
+        (classType as? Symbol.ClassSymbol)?.run {
+            (interfaces + superclass).forEach {
+                val element = it.asElement() ?: return@forEach
+                visitClass(parentChain, seenTypesByPolicy, element, element, newPolicyExceptions)
+            }
+        }
+
+        if (isRegularClass && !anyError && allowFinalClassesFinalFields &&
+            !classType.modifiers.contains(Modifier.FINAL)
         ) {
             printError(parentChain, elementToPrint, MessageUtils.classNotFinalFailure(className))
             return true
@@ -299,16 +323,21 @@
         parentPolicyExceptions: Set<Immutable.Policy.Exception>,
         nonInterfaceClassFailure: () -> String = { MessageUtils.nonInterfaceReturnFailure() },
     ): Boolean {
+        // Skip if the symbol being considered is itself ignored
+        if (isIgnored(symbol)) return false
+
+        // Skip if the type being checked, like for a typeArg or return type, is ignored
+        if (isIgnored(type)) return false
+
+        // Skip if that typeArg is itself ignored when inspected at the class header level
+        if (isIgnored(type.asElement())) return false
+
         if (type.isPrimitive) return false
         if (type.isPrimitiveOrVoid) {
             printError(parentChain, symbol, MessageUtils.voidReturnFailure())
             return true
         }
 
-        if (ignoredTypes.any { processingEnv.typeUtils.isAssignable(type, it) }) {
-            return false
-        }
-
         val policyAnnotation = symbol.getAnnotation(Immutable.Policy::class.java)
         val newPolicyExceptions = parentPolicyExceptions + policyAnnotation?.exceptions.orEmpty()
 
@@ -333,6 +362,8 @@
         var anyError = false
 
         type.typeArguments.forEachIndexed { index, typeArg ->
+            if (isIgnored(typeArg.asElement())) return@forEachIndexed
+
             val argError =
                 visitType(parentChain, seenTypesByPolicy, symbol, typeArg, newPolicyExceptions) {
                     MessageUtils.nonInterfaceReturnFailure(
@@ -355,16 +386,38 @@
         message: String,
     ) = processingEnv.messager.printMessage(
         Diagnostic.Kind.ERROR,
-        // Drop one from the parent chain so that the directly enclosing class isn't logged.
-        // It exists in the list at this point in the traversal so that further children can
-        // include the right reference.
-        parentChain.dropLast(1).joinToString() + "\n\t" + message,
+        parentChain.plus(element.simpleName).joinToString() + "\n\t " + message,
         element,
     )
 
     private fun ProcessingEnvironment.erasedType(typeName: String) =
         elementUtils.getTypeElement(typeName)?.asType()?.let(typeUtils::erasure)
 
-    private fun isIgnored(symbol: Symbol) =
-        symbol.getAnnotation(Immutable.Ignore::class.java) != null
-}
\ No newline at end of file
+    private fun isIgnored(type: Type) =
+        (type.getAnnotation(Immutable.Ignore::class.java) != null)
+                || (ignoredSuperTypes.any { type.isAssignable(it) })
+                || (ignoredExactTypes.any { type.isSameType(it) })
+
+    private fun isIgnored(symbol: Symbol) = when {
+        // Anything annotated as @Ignore is always ignored
+        symbol.getAnnotation(Immutable.Ignore::class.java) != null -> true
+        // Then ignore exact types, regardless of what kind they are
+        ignoredExactTypes.any { symbol.type.isSameType(it) } -> true
+        // Then only allow methods through, since other types (fields) are usually a failure
+        symbol.getKind() != ElementKind.METHOD -> false
+        // Finally, check for any ignored super types
+        else -> ignoredSuperTypes.any { symbol.type.isAssignable(it) }
+    }
+
+    private fun TypeMirror.isAssignable(type: TypeMirror) = try {
+        processingEnv.typeUtils.isAssignable(this, type)
+    } catch (ignored: Exception) {
+        false
+    }
+
+    private fun TypeMirror.isSameType(type: TypeMirror) = try {
+        processingEnv.typeUtils.isSameType(this, type)
+    } catch (ignored: Exception) {
+        false
+    }
+}
diff --git a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
index f26357f..43caa45 100644
--- a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
+++ b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
@@ -90,7 +90,7 @@
 
     @Test
     fun validInterface() = test(
-        JavaFileObjects.forSourceString(
+        source = JavaFileObjects.forSourceString(
             "$PACKAGE_PREFIX.$DATA_CLASS_NAME",
             /* language=JAVA */ """
                 package $PACKAGE_PREFIX;
@@ -227,49 +227,150 @@
             nonInterfaceReturnFailure(line = 9),
             nonInterfaceReturnFailure(line = 10, index = 0),
             classNotFinalFailure(line = 13, "NonFinalClassFinalFields"),
-        ), otherErrors = listOf(
-            memberNotMethodFailure(line = 4) to FINAL_CLASSES[1],
-            memberNotMethodFailure(line = 4) to FINAL_CLASSES[3],
+        ), otherErrors = mapOf(
+            FINAL_CLASSES[1] to listOf(
+                memberNotMethodFailure(line = 4),
+            ),
+            FINAL_CLASSES[3] to listOf(
+                memberNotMethodFailure(line = 4),
+            ),
+        )
+    )
+
+    @Test
+    fun superClass() {
+        val superClass = JavaFileObjects.forSourceString(
+            "$PACKAGE_PREFIX.SuperClass",
+            /* language=JAVA */ """
+            package $PACKAGE_PREFIX;
+
+            import java.util.List;
+
+            public interface SuperClass {
+                InnerClass getInnerClassOne();
+
+                final class InnerClass {
+                    public String innerField;
+                }
+            }
+            """.trimIndent()
+        )
+
+        val dataClass = JavaFileObjects.forSourceString(
+            "$PACKAGE_PREFIX.$DATA_CLASS_NAME",
+            /* language=JAVA */ """
+            package $PACKAGE_PREFIX;
+
+            import java.util.List;
+
+            @Immutable
+            public interface $DATA_CLASS_NAME extends SuperClass {
+                String[] getArray();
+            }
+            """.trimIndent()
+        )
+
+        test(
+            sources = arrayOf(superClass, dataClass),
+            fileToErrors = mapOf(
+                superClass to listOf(
+                    classNotImmutableFailure(line = 5, className = "SuperClass"),
+                    nonInterfaceReturnFailure(line = 6),
+                    nonInterfaceClassFailure(8),
+                    classNotImmutableFailure(line = 8, className = "InnerClass"),
+                    memberNotMethodFailure(line = 9),
+                ),
+                dataClass to listOf(
+                    arrayFailure(line = 7),
+                )
+            )
+        )
+    }
+
+    @Test
+    fun ignoredClass() = test(
+        JavaFileObjects.forSourceString(
+            "$PACKAGE_PREFIX.$DATA_CLASS_NAME",
+            /* language=JAVA */ """
+            package $PACKAGE_PREFIX;
+
+            import java.util.List;
+            import java.util.Map;
+
+            @Immutable
+            public interface $DATA_CLASS_NAME {
+                IgnoredClass getInnerClassOne();
+                NotIgnoredClass getInnerClassTwo();
+                Map<String, IgnoredClass> getInnerClassThree();
+                Map<String, NotIgnoredClass> getInnerClassFour();
+
+                @Immutable.Ignore
+                final class IgnoredClass {
+                    public String innerField;
+                }
+
+                final class NotIgnoredClass {
+                    public String innerField;
+                }
+            }
+            """.trimIndent()
+        ), errors = listOf(
+            nonInterfaceReturnFailure(line = 9),
+            nonInterfaceReturnFailure(line = 11, prefix = "Value NotIgnoredClass"),
+            classNotImmutableFailure(line = 18, className = "NotIgnoredClass"),
+            nonInterfaceClassFailure(line = 18),
+            memberNotMethodFailure(line = 19),
         )
     )
 
     private fun test(
         source: JavaFileObject,
         errors: List<CompilationError>,
-        otherErrors: List<Pair<CompilationError, JavaFileObject>> = emptyList(),
+        otherErrors: Map<JavaFileObject, List<CompilationError>> = emptyMap(),
+    ) = test(
+        sources = arrayOf(source),
+        fileToErrors = otherErrors + (source to errors),
+    )
+
+    private fun test(
+        vararg sources: JavaFileObject,
+        fileToErrors: Map<JavaFileObject, List<CompilationError>> = emptyMap(),
     ) {
         val compilation = javac()
             .withProcessors(ImmutabilityProcessor())
-            .compile(FINAL_CLASSES + ANNOTATION + listOf(source))
-        val allErrors = otherErrors + errors.map { it to source }
-        allErrors.forEach { (error, file) ->
-            try {
-                assertThat(compilation)
-                    .hadErrorContaining(error.message)
-                    .inFile(file)
-                    .onLine(error.line)
-            } catch (e: AssertionError) {
-                // Wrap the exception so that the line number is logged
-                val wrapped = AssertionError("Expected $error, ${e.message}").apply {
-                    stackTrace = e.stackTrace
-                }
+            .compile(FINAL_CLASSES + ANNOTATION + sources)
 
-                // Wrap again with Expect so that all errors are reported. This is very bad code
-                // but can only be fixed by updating compile-testing with a better Truth Subject
-                // implementation.
-                expect.that(wrapped).isNull()
+        fileToErrors.forEach { (file, errors) ->
+            errors.forEach { error ->
+                try {
+                    assertThat(compilation)
+                        .hadErrorContaining(error.message)
+                        .inFile(file)
+                        .onLine(error.line)
+                } catch (e: AssertionError) {
+                    // Wrap the exception so that the line number is logged
+                    val wrapped = AssertionError("Expected $error, ${e.message}").apply {
+                        stackTrace = e.stackTrace
+                    }
+
+                    // Wrap again with Expect so that all errors are reported. This is very bad code
+                    // but can only be fixed by updating compile-testing with a better Truth Subject
+                    // implementation.
+                    expect.that(wrapped).isNull()
+                }
             }
         }
 
-        try {
-            assertThat(compilation).hadErrorCount(allErrors.size)
-        } catch (e: AssertionError) {
+        expect.that(compilation.errors().size).isEqualTo(fileToErrors.values.sumOf { it.size })
+
+        if (expect.hasFailures()) {
             expect.withMessage(
                 compilation.errors()
+                    .sortedBy { it.lineNumber }
                     .joinToString(separator = "\n") {
                         "${it.lineNumber}: ${it.getMessage(Locale.ENGLISH)?.trim()}"
                     }
-            ).that(e).isNull()
+            ).fail()
         }
     }
 
@@ -307,4 +408,4 @@
         val line: Long,
         val message: String,
     )
-}
\ No newline at end of file
+}
diff --git a/tools/processors/staledataclass/Android.bp b/tools/processors/staledataclass/Android.bp
index 1e50976..2169c49 100644
--- a/tools/processors/staledataclass/Android.bp
+++ b/tools/processors/staledataclass/Android.bp
@@ -22,17 +22,13 @@
     static_libs: [
         "codegen-version-info",
     ],
-    // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java language level 8 (see b/139342589):
-    java_version: "1.8",
-    openjdk9: {
-        javacflags: [
-            "--add-modules=jdk.compiler",
-            "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
-            "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
-        ],
-    },
+    javacflags: [
+        "--add-modules=jdk.compiler",
+        "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    ],
 
     use_tools_jar: true,
 }
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
index 27a8853..1cef5b0 100644
--- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
 
 package android.processor.staledataclass