Merge "Add existing and visibility check in addPackageDependency"
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/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/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index be0e025..6af24be 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -227,6 +227,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 +512,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..c622259 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,8 @@
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 
+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;
@@ -57,6 +59,8 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
@@ -126,6 +130,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;
@@ -144,6 +149,8 @@
 import com.android.server.LocalServices;
 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 +206,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 +219,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 +308,7 @@
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     IBinder.DeathRecipient mListenerDeathRecipient;
     Intent mTimeTickIntent;
+    Bundle mTimeTickOptions;
     IAlarmListener mTimeTickTrigger;
     PendingIntent mDateChangeSender;
     boolean mInteractive = true;
@@ -1881,9 +1901,12 @@
 
             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));
+            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.  Use the
             // most recent of Build.TIME, the root file system's timestamp, and the
@@ -1909,7 +1932,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 +1946,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 */ }
@@ -2117,13 +2142,18 @@
         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));
+
+            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(currentTimeMillis);
+                final int newTzOffset = timeZone.getOffset(millis);
+                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 +2161,30 @@
         }
     }
 
-    void setTimeZoneImpl(String tz) {
-        if (TextUtils.isEmpty(tz)) {
+    void setTimeZoneImpl(String tzId, @TimeZoneConfidence int confidence) {
+        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);
 
-            // 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 +2197,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,
@@ -2685,6 +2714,11 @@
         }
 
         @Override
+        public void setTimeZone(String tzId, @TimeZoneConfidence int confidence) {
+            setTimeZoneImpl(tzId, confidence);
+        }
+
+        @Override
         public void registerInFlightListener(InFlightListener callback) {
             synchronized (mLock) {
                 mInFlightListeners.add(callback);
@@ -2969,7 +3003,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);
             } finally {
                 Binder.restoreCallingIdentity(oldId);
             }
@@ -4273,6 +4311,15 @@
     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);
 
@@ -4541,8 +4588,18 @@
             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 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 setKernelTime(long millis) {
@@ -5009,13 +5066,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();
             }
         }
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 1e13dbf..1775d90 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -882,6 +882,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 +934,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 +989,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 +1312,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 +1334,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);
             }
         }
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 fcfb45c..78ab06c 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);
     }
@@ -1220,27 +1231,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();
@@ -1382,7 +1397,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/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index c13e1dd9..4a26d21 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -81,6 +81,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
@@ -164,6 +165,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;
@@ -353,6 +358,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) {
@@ -525,7 +538,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 +566,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 +595,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 +612,7 @@
                     mUidToPackageCache.remove(pkgInfo.uid);
                 }
             }
+            mInstallers.delete(userId);
             mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId);
         }
@@ -746,11 +769,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);
@@ -1360,6 +1421,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..322e824 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;
@@ -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/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/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/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 444f91d..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;
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 ea61962b..12d579e 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;
@@ -9831,6 +9832,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 +11614,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 +11962,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";
@@ -18644,8 +18666,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
   }
@@ -20063,6 +20087,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
@@ -22433,6 +22458,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";
@@ -45193,6 +45219,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();
@@ -49254,6 +49281,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
@@ -51878,10 +51906,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
@@ -53314,6 +53343,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);
@@ -53457,6 +53487,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);
@@ -53542,8 +53573,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..f221eca 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3951,6 +3951,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 +6673,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 fb2c9e4..9f33331 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 {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8f6bfd3..812e422 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,7 +784,6 @@
         mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
-        mIsAccessibilityTool = other.mIsAccessibilityTool;
     }
 
     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/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..626b7d3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PermissionMethod;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -292,6 +293,7 @@
             boolean allowAll, int allowMode, String name, String callerPackage);
 
     /** Checks if the calling binder pid as the permission. */
+    @PermissionMethod
     public abstract void enforceCallingPermission(String permission, String func);
 
     /** Returns the current user id. */
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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 980b79b..b4abd3c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -770,4 +770,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..7c357a6 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -173,6 +173,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();
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 e5c080a..d5b85cd 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -111,6 +111,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;
@@ -1524,6 +1525,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/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 357bf59..2bd36b8 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -450,7 +450,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/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d4c9a42..fadfa5c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -445,8 +445,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..c658b3e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -51,6 +51,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionMethod;
 import android.content.res.AssetManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -6049,6 +6050,15 @@
     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";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6066,6 +6076,7 @@
      */
     @CheckResult(suggest="#enforcePermission(String,int,int,String)")
     @PackageManager.PermissionResult
+    @PermissionMethod
     public abstract int checkPermission(@NonNull String permission, int pid, int uid);
 
     /** @hide */
@@ -6098,6 +6109,7 @@
      */
     @CheckResult(suggest="#enforceCallingPermission(String,String)")
     @PackageManager.PermissionResult
+    @PermissionMethod
     public abstract int checkCallingPermission(@NonNull String permission);
 
     /**
@@ -6118,6 +6130,7 @@
      */
     @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
     @PackageManager.PermissionResult
+    @PermissionMethod
     public abstract int checkCallingOrSelfPermission(@NonNull String permission);
 
     /**
@@ -6146,6 +6159,7 @@
      *
      * @see #checkPermission(String, int, int)
      */
+    @PermissionMethod
     public abstract void enforcePermission(
             @NonNull String permission, int pid, int uid, @Nullable String message);
 
@@ -6167,6 +6181,7 @@
      *
      * @see #checkCallingPermission(String)
      */
+    @PermissionMethod
     public abstract void enforceCallingPermission(
             @NonNull String permission, @Nullable String message);
 
@@ -6183,6 +6198,7 @@
      *
      * @see #checkCallingOrSelfPermission(String)
      */
+    @PermissionMethod
     public abstract void enforceCallingOrSelfPermission(
             @NonNull String permission, @Nullable String message);
 
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..021b2e1
--- /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.
+ *
+ * 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/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/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/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/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index dfb4236..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;
@@ -1315,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/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/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/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 a863a87..adae034 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;
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3..c943a3d 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();
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/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/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/core/java/android/service/credentials/CreateCredentialResponse.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
copy to core/java/android/service/credentials/CreateCredentialResponse.aidl
index 44c0496..73c9147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/core/java/android/service/credentials/CreateCredentialResponse.aidl
@@ -14,14 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package android.service.credentials;
 
-/**
- * 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,
-)
+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/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/text/Layout.java b/core/java/android/text/Layout.java
index b5f7c54..dbb41f4 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1841,7 +1841,7 @@
         // Find the first line whose vertical center is below the top of the area.
         int startLine = getLineForVertical((int) area.top);
         int startLineTop = getLineTop(startLine);
-        int startLineBottom = getLineBottomWithoutSpacing(startLine);
+        int startLineBottom = getLineBottom(startLine, /* includeLineSpacing= */ false);
         if (area.top > (startLineTop + startLineBottom) / 2f) {
             startLine++;
             if (startLine >= getLineCount()) {
@@ -1854,7 +1854,7 @@
         // Find the last line whose vertical center is above the bottom of the area.
         int endLine = getLineForVertical((int) area.bottom);
         int endLineTop = getLineTop(endLine);
-        int endLineBottom = getLineBottomWithoutSpacing(endLine);
+        int endLineBottom = getLineBottom(endLine, /* includeLineSpacing= */ false);
         if (area.bottom < (endLineTop + endLineBottom) / 2f) {
             endLine--;
         }
@@ -2229,17 +2229,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 +2398,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 +2534,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 +2563,7 @@
             }
 
             top = getLineTop(endline);
-            bottom = getLineBottomWithoutSpacing(endline);
+            bottom = getLineBottom(endline, /* includeLineSpacing= */ false);
 
             addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
 
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/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/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/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b2a26fa..30dc0c2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -67,6 +67,7 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.ITaskFpsCallback;
+import android.window.ScreenCapture;
 
 /**
  * System private interface to the window manager.
@@ -226,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
@@ -968,4 +978,11 @@
      * treatment.
      */
     boolean isLetterboxBackgroundMultiColored();
+
+    /**
+     * Captures the entire display specified by the displayId using the args provided. If the args
+     * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
+     */
+    oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
+            in ScreenCapture.ScreenCaptureListener listener);
 }
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..5ec7962 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -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 2f7ae49..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;
@@ -2015,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();
     }
 
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 6ac3992..bc665cf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15903,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);
     }
 
     /**
@@ -24656,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..455f258 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) {
             }
         }
@@ -1766,9 +1758,6 @@
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
-            if (!mAppVisible) {
-                WindowManagerGlobal.trimForeground();
-            }
             AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
         }
     }
@@ -5813,7 +5802,8 @@
 
         // tell the window manager
         try {
-            mWindowSession.setInTouchMode(inTouchMode);
+            IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+            windowManager.setInTouchMode(inTouchMode, getDisplayId());
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
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..5b760a4 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;
@@ -1578,16 +1577,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());
     }
 
@@ -3633,6 +3632,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 41d00a2..d11fa5f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7650,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
@@ -9330,9 +9331,58 @@
         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.)
+                while (start > 0 && TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)) {
+                    start -= Character.charCount(codePointBeforeStart);
+                    codePointBeforeStart = Character.codePointBefore(mText, start);
+                }
+            } 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.)
+                while (end < mText.length()
+                        && TextUtils.isWhitespaceExceptNewline(codePointAtEnd)) {
+                    end += Character.charCount(codePointAtEnd);
+                    codePointAtEnd = Character.codePointAt(mText, end);
+                }
+            }
+        }
+
+        getEditableText().delete(start, end);
+        Selection.setSelection(getEditableText(), start);
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
@@ -9341,7 +9391,7 @@
         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)) {
@@ -9369,7 +9419,7 @@
             // Both points are above the top of the first line.
             return handleGestureFailure(gesture);
         }
-        if (yMin > mLayout.getLineBottomWithoutSpacing(line)) {
+        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);
@@ -9423,7 +9473,7 @@
 
         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)) {
@@ -12172,6 +12222,11 @@
         }
     }
 
+    @Override
+    public boolean isAutoHandwritingEnabled() {
+        return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
+    }
+
     /** @hide */
     @Override
     public boolean isStylusHandwritingAvailable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/core/java/android/window/ScreenCapture.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
copy to core/java/android/window/ScreenCapture.aidl
index 44c0496..267a7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/core/java/android/window/ScreenCapture.aidl
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package android.window;
 
-/**
- * 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,
-)
+/** @hide */
+parcelable ScreenCapture.CaptureArgs;
+
+/** @hide */
+parcelable ScreenCapture.ScreenshotHardwareBuffer;
+
+/** @hide */
+parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 887d027..8a7efb9 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,11 +24,17 @@
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
+import android.util.Pair;
 import android.view.SurfaceControl;
 
+import libcore.util.NativeAllocationRegistry;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Handles display and layer captures for the system.
@@ -37,20 +43,26 @@
  */
 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,
-            ScreenCaptureListener captureListener);
+            long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
-            ScreenCaptureListener captureListener);
+            long captureListener);
+    private static native long nativeCreateScreenCaptureListener(
+            Consumer<ScreenshotHardwareBuffer> consumer);
+    private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
+    private static native long nativeReadListenerFromParcel(Parcel in);
+    private static native long getNativeListenerFinalizer();
 
     /**
-     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureArgs     Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureDisplay(captureArgs, captureListener);
+        return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
     }
 
     /**
@@ -61,28 +73,29 @@
      */
     public static ScreenshotHardwareBuffer captureDisplay(
             DisplayCaptureArgs captureArgs) {
-        SyncScreenCaptureListener
-                screenCaptureListener = new SyncScreenCaptureListener();
-
-        int status = captureDisplay(captureArgs, screenCaptureListener);
+        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;
+        }
     }
 
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer            The root layer to capture.
-     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
-     *                         Rect()' or null if no cropping is desired. If the root layer does not
-     *                         have a buffer or a crop set, then a non-empty source crop must be
-     *                         specified.
-     * @param frameScale       The desired scale of the returned buffer; the raw
-     *                         screen will be scaled up/down.
-     *
+     * @param layer      The root layer to capture.
+     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+     *                   Rect()' or null if no cropping is desired. If the root layer does not
+     *                   have a buffer or a crop set, then a non-empty source crop must be
+     *                   specified.
+     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+     *                   up/down.
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -94,15 +107,14 @@
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer            The root layer to capture.
-     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
-     *                         Rect()' or null if no cropping is desired. If the root layer does not
-     *                         have a buffer or a crop set, then a non-empty source crop must be
-     *                         specified.
-     * @param frameScale       The desired scale of the returned buffer; the raw
-     *                         screen will be scaled up/down.
-     * @param format           The desired pixel format of the returned buffer.
-     *
+     * @param layer      The root layer to capture.
+     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+     *                   Rect()' or null if no cropping is desired. If the root layer does not
+     *                   have a buffer or a crop set, then a non-empty source crop must be
+     *                   specified.
+     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+     *                   up/down.
+     * @param format     The desired pixel format of the returned buffer.
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -120,21 +132,24 @@
     /**
      * @hide
      */
-    public static ScreenshotHardwareBuffer captureLayers(
-            LayerCaptureArgs captureArgs) {
-        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
-
-        int status = captureLayers(captureArgs, screenCaptureListener);
+    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;
+        }
     }
 
     /**
      * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
      * handles to exclude.
+     *
      * @hide
      */
     public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -150,24 +165,13 @@
     }
 
     /**
-     * @param captureArgs Arguments about how to take the screenshot
+     * @param captureArgs     Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureLayers(captureArgs, captureListener);
-    }
-
-    /**
-     * @hide
-     */
-    public interface ScreenCaptureListener {
-        /**
-         * The callback invoked when the screen capture is complete.
-         * @param hardwareBuffer Data containing info about the screen capture.
-         */
-        void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
+        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
     }
 
     /**
@@ -190,15 +194,16 @@
             mContainsHdrLayers = containsHdrLayers;
         }
 
-       /**
-        * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
-        * @param hardwareBuffer The existing HardwareBuffer object
-        * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
-        * @param containsSecureLayers Indicates whether this graphic buffer contains captured
-        *                             contents of secure layers, in which case the screenshot
-        *                             should not be persisted.
-        * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
-        */
+        /**
+         * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+         *
+         * @param hardwareBuffer       The existing HardwareBuffer object
+         * @param namedColorSpace      Integer value of a named color space {@link ColorSpace.Named}
+         * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+         *                             contents of secure layers, in which case the screenshot
+         *                             should not be persisted.
+         * @param containsHdrLayers    Indicates whether this graphic buffer contains HDR content.
+         */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
                 int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
             ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -220,6 +225,7 @@
         public boolean containsSecureLayers() {
             return mContainsSecureLayers;
         }
+
         /**
          * Returns whether the screenshot contains at least one HDR layer.
          * This information may be useful for informing the display whether this screenshot
@@ -234,7 +240,7 @@
          * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
          * into
          * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
-         *
+         * <p>
          * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
          * directly
          * use the {@link HardwareBuffer} directly.
@@ -250,44 +256,23 @@
         }
     }
 
-    private static class SyncScreenCaptureListener implements ScreenCaptureListener {
-        private static final int SCREENSHOT_WAIT_TIME_S = 1;
-        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
-        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-
-        @Override
-        public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
-            mScreenshotHardwareBuffer = hardwareBuffer;
-            mCountDownLatch.countDown();
-        }
-
-        private ScreenshotHardwareBuffer waitForScreenshot() {
-            try {
-                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to wait for screen capture result", e);
-            }
-
-            return mScreenshotHardwareBuffer;
-        }
-    }
-
     /**
      * A common arguments class used for various screenshot requests. This contains arguments that
      * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
+     *
      * @hide
      */
-    private abstract static class CaptureArgs {
-        private final int mPixelFormat;
-        private final Rect mSourceCrop = new Rect();
-        private final float mFrameScaleX;
-        private final float mFrameScaleY;
-        private final boolean mCaptureSecureLayers;
-        private final boolean mAllowProtected;
-        private final long mUid;
-        private final boolean mGrayscale;
+    public static class CaptureArgs implements Parcelable {
+        public final int mPixelFormat;
+        public final Rect mSourceCrop = new Rect();
+        public final float mFrameScaleX;
+        public final float mFrameScaleY;
+        public final boolean mCaptureSecureLayers;
+        public final boolean mAllowProtected;
+        public final long mUid;
+        public final boolean mGrayscale;
 
-        private CaptureArgs(Builder<? extends Builder<?>> builder) {
+        private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
             mSourceCrop.set(builder.mSourceCrop);
             mFrameScaleX = builder.mFrameScaleX;
@@ -298,12 +283,23 @@
             mGrayscale = builder.mGrayscale;
         }
 
+        private CaptureArgs(Parcel in) {
+            mPixelFormat = in.readInt();
+            mSourceCrop.readFromParcel(in);
+            mFrameScaleX = in.readFloat();
+            mFrameScaleY = in.readFloat();
+            mCaptureSecureLayers = in.readBoolean();
+            mAllowProtected = in.readBoolean();
+            mUid = in.readLong();
+            mGrayscale = in.readBoolean();
+        }
+
         /**
          * The Builder class used to construct {@link CaptureArgs}
          *
-         * @param <T> A builder that extends {@link Builder}
+         * @param <T> A builder that extends {@link CaptureArgs.Builder}
          */
-        abstract static class Builder<T extends Builder<T>> {
+        public static class Builder<T extends CaptureArgs.Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
             private float mFrameScaleX = 1;
@@ -314,6 +310,14 @@
             private boolean mGrayscale;
 
             /**
+             * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
+             * valid.
+             */
+            public CaptureArgs build() {
+                return new CaptureArgs(this);
+            }
+
+            /**
              * The desired pixel format of the returned buffer.
              */
             public T setPixelFormat(int pixelFormat) {
@@ -395,15 +399,47 @@
             /**
              * Each sub class should return itself to allow the builder to chain properly
              */
-            abstract T getThis();
+            T getThis() {
+                return (T) this;
+            }
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mPixelFormat);
+            mSourceCrop.writeToParcel(dest, flags);
+            dest.writeFloat(mFrameScaleX);
+            dest.writeFloat(mFrameScaleY);
+            dest.writeBoolean(mCaptureSecureLayers);
+            dest.writeBoolean(mAllowProtected);
+            dest.writeLong(mUid);
+            dest.writeBoolean(mGrayscale);
+        }
+
+        public static final Parcelable.Creator<CaptureArgs> CREATOR =
+                new Parcelable.Creator<CaptureArgs>() {
+                    @Override
+                    public CaptureArgs createFromParcel(Parcel in) {
+                        return new CaptureArgs(in);
+                    }
+
+                    @Override
+                    public CaptureArgs[] newArray(int size) {
+                        return new CaptureArgs[size];
+                    }
+                };
     }
 
     /**
      * The arguments class used to make display capture requests.
      *
-     * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
      * @hide
+     * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
      */
     public static class DisplayCaptureArgs extends CaptureArgs {
         private final IBinder mDisplayToken;
@@ -488,8 +524,8 @@
     /**
      * The arguments class used to make layer capture requests.
      *
-     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
      * @hide
+     * @see #nativeCaptureLayers(LayerCaptureArgs, long)
      */
     public static class LayerCaptureArgs extends CaptureArgs {
         private final long mNativeLayer;
@@ -530,6 +566,17 @@
                 return new LayerCaptureArgs(this);
             }
 
+            public Builder(SurfaceControl layer, CaptureArgs args) {
+                setLayer(layer);
+                setPixelFormat(args.mPixelFormat);
+                setSourceCrop(args.mSourceCrop);
+                setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
+                setCaptureSecureLayers(args.mCaptureSecureLayers);
+                setAllowProtected(args.mAllowProtected);
+                setUid(args.mUid);
+                setGrayscale(args.mGrayscale);
+            }
+
             public Builder(SurfaceControl layer) {
                 setLayer(layer);
             }
@@ -542,7 +589,6 @@
                 return this;
             }
 
-
             /**
              * An array of layer handles to exclude.
              */
@@ -564,8 +610,109 @@
             Builder getThis() {
                 return this;
             }
-
         }
     }
 
+    /**
+     * 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;
+        private static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
+
+        /**
+         * @param consumer The callback invoked when the screen capture is complete.
+         */
+        public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+            mNativeObject = nativeCreateScreenCaptureListener(consumer);
+            sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        private ScreenCaptureListener(Parcel in) {
+            if (in.readBoolean()) {
+                mNativeObject = nativeReadListenerFromParcel(in);
+                sRegistry.registerNativeAllocation(this, mNativeObject);
+            } else {
+                mNativeObject = 0;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            if (mNativeObject == 0) {
+                dest.writeBoolean(false);
+            } else {
+                dest.writeBoolean(true);
+                nativeWriteListenerToParcel(mNativeObject, dest);
+            }
+        }
+
+        public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
+                new Parcelable.Creator<ScreenCaptureListener>() {
+                    @Override
+                    public ScreenCaptureListener createFromParcel(Parcel in) {
+                        return new ScreenCaptureListener(in);
+                    }
+
+                    @Override
+                    public ScreenCaptureListener[] newArray(int size) {
+                        return new ScreenCaptureListener[0];
+                    }
+                };
+    }
+
+    /**
+     * 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 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 ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+
+        private void setScreenshotHardwareBuffer(
+                ScreenshotHardwareBuffer screenshotHardwareBuffer) {
+            mScreenshotHardwareBuffer = screenshotHardwareBuffer;
+            mCountDownLatch.countDown();
+        }
+
+        /**
+         * 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 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;
+            }
+        }
+    }
 }
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/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 40d192e..76f33a6 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -541,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);
@@ -614,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;
@@ -651,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);
     }
 
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..9f15469 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)")
@@ -97,6 +97,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/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_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b5a78b0..b60ec9f 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -631,7 +631,7 @@
                                    jshort density, jobject typed_value,
                                    jboolean resolve_references) {
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
-  ResourceTimer _tag(ResourceTimer::Counter::GetResourceValue);
+  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()) {
@@ -1234,7 +1234,7 @@
   }
 
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
-  ResourceTimer _tag(ResourceTimer::Counter::RetrieveAttributes);
+  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),
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_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 3bada15..c1929c6 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -61,9 +61,8 @@
 } gLayerCaptureArgsClassInfo;
 
 static struct {
-    jclass clazz;
-    jmethodID onScreenCaptureComplete;
-} gScreenCaptureListenerClassInfo;
+    jmethodID accept;
+} gConsumerClassInfo;
 
 static struct {
     jclass clazz;
@@ -98,14 +97,14 @@
 public:
     explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
         env->GetJavaVM(&mVm);
-        mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
-        LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
+        mConsumerObject = env->NewGlobalRef(jobject);
+        LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
     }
 
     ~ScreenCaptureListenerWrapper() {
-        if (mScreenCaptureListenerObject) {
-            getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
-            mScreenCaptureListenerObject = nullptr;
+        if (mConsumerObject) {
+            getenv()->DeleteGlobalRef(mConsumerObject);
+            mConsumerObject = nullptr;
         }
     }
 
@@ -113,9 +112,8 @@
             const gui::ScreenCaptureResults& captureResults) override {
         JNIEnv* env = getenv();
         if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
-            env->CallVoidMethod(mScreenCaptureListenerObject,
-                                gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
-            checkAndClearException(env, "onScreenCaptureComplete");
+            env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
+            checkAndClearException(env, "accept");
             return binder::Status::ok();
         }
         captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -130,17 +128,15 @@
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
-        env->CallVoidMethod(mScreenCaptureListenerObject,
-                            gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
-                            screenshotHardwareBuffer);
-        checkAndClearException(env, "onScreenCaptureComplete");
+        env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
+        checkAndClearException(env, "accept");
         env->DeleteLocalRef(jhardwareBuffer);
         env->DeleteLocalRef(screenshotHardwareBuffer);
         return binder::Status::ok();
     }
 
 private:
-    jobject mScreenCaptureListenerObject;
+    jobject mConsumerObject;
     JavaVM* mVm;
 
     JNIEnv* getenv() {
@@ -194,7 +190,7 @@
 }
 
 static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
-                                 jobject screenCaptureListenerObject) {
+                                 jlong screenCaptureListenerObject) {
     const DisplayCaptureArgs captureArgs =
             displayCaptureArgsFromObject(env, displayCaptureArgsObject);
 
@@ -202,13 +198,13 @@
         return BAD_VALUE;
     }
 
-    sp<IScreenCaptureListener> captureListener =
-            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+    sp<gui::IScreenCaptureListener> captureListener =
+            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
     return ScreenshotClient::captureDisplay(captureArgs, captureListener);
 }
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
-                                jobject screenCaptureListenerObject) {
+                                jlong screenCaptureListenerObject) {
     LayerCaptureArgs captureArgs;
     getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
     SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -238,21 +234,70 @@
         }
     }
 
-    sp<IScreenCaptureListener> captureListener =
-            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+    sp<gui::IScreenCaptureListener> captureListener =
+            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
     return ScreenshotClient::captureLayers(captureArgs, captureListener);
 }
 
+static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
+    sp<gui::IScreenCaptureListener> listener =
+            sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
+    listener->incStrong((void*)nativeCreateScreenCaptureListener);
+    return reinterpret_cast<jlong>(listener.get());
+}
+
+static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
+                                        jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+    ScreenCaptureListenerWrapper* const self =
+            reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
+    if (self != nullptr) {
+        parcel->writeStrongBinder(IInterface::asBinder(self));
+    }
+}
+
+static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return 0;
+    }
+    sp<gui::IScreenCaptureListener> listener =
+            interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
+    if (listener == nullptr) {
+        return 0;
+    }
+    listener->incStrong((void*)nativeCreateScreenCaptureListener);
+    return reinterpret_cast<jlong>(listener.get());
+}
+
+void destroyNativeListener(void* ptr) {
+    ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
+    listener->decStrong((void*)nativeCreateScreenCaptureListener);
+}
+
+static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sScreenCaptureMethods[] = {
         // clang-format off
-   {"nativeCaptureDisplay",
-            "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+    {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
             (void*)nativeCaptureDisplay },
-    {"nativeCaptureLayers",
-            "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+    {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
             (void*)nativeCaptureLayers },
+    {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
+            (void*)nativeCreateScreenCaptureListener },
+    {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
+    {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
+            (void*)nativeReadListenerFromParcel },
+    {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
         // clang-format on
 };
 
@@ -293,12 +338,8 @@
     gLayerCaptureArgsClassInfo.childrenOnly =
             GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
 
-    jclass screenCaptureListenerClazz =
-            FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
-    gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
-    gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
-            GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
-                             "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
+    jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
+    gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
 
     jclass screenshotGraphicsBufferClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
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 78c0710..ded1b89 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                           -->
@@ -3509,7 +3511,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. -->
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 da132d9..934ae21 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -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 fdda0a7..9007d8e 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/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-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 cc393ab..1bce8fe 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/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-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 791958b..98f8ff2 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -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. 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>
@@ -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>
@@ -123,7 +123,7 @@
     <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 />
@@ -140,7 +140,7 @@
     <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">"Apelează prin Wi-Fi"</string>
-    <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Apelați prin rețeaua mobilă"</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,7 +171,7 @@
     <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>
@@ -181,13 +181,13 @@
     <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="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>
@@ -199,10 +199,10 @@
     <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">"Activează 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_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">"Activează"</string>
     <string name="me" msgid="6207584824693813140">"Eu"</string>
@@ -224,11 +224,11 @@
     <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">"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>
@@ -245,7 +245,7 @@
     <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">"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>
@@ -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">"să acceseze agenda"</string>
     <string name="permgrouplab_location" msgid="1858277002233964394">"Locație"</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>
@@ -314,7 +314,7 @@
     <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>
@@ -323,19 +323,19 @@
     <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_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,11 +354,11 @@
     <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 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>
@@ -372,7 +372,7 @@
     <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>
@@ -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>
@@ -420,7 +420,7 @@
     <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, 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,10 +436,10 @@
     <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>
@@ -450,7 +450,7 @@
     <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>
@@ -463,9 +463,9 @@
     <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">"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 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="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>
@@ -502,8 +502,8 @@
     <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-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-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="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>
@@ -517,13 +517,13 @@
     <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">"schimbă starea WiMAX"</string>
@@ -532,8 +532,8 @@
     <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ă 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 î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="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,49 +550,49 @@
     <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="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">"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="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">"Confirmați-vă identitatea"</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">"Folosește 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_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">"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_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>
@@ -612,7 +612,7 @@
     <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">"Folosește amprenta"</string>
@@ -625,15 +625,15 @@
     <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_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">"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) -->
@@ -672,14 +672,14 @@
     <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. 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">"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">"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_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>
@@ -718,7 +718,7 @@
     <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>
@@ -750,21 +750,21 @@
     <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="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">"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_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">"Modifică blocarea ecranului."</string>
     <string name="policylab_forceLock" msgid="7360335502968476434">"Să blocheze ecranul"</string>
@@ -920,7 +920,7 @@
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Apasă Meniu pentru deblocare."</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>
@@ -933,7 +933,7 @@
     <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>
@@ -944,31 +944,31 @@
     <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_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">"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="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 prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</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_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">"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_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">"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">"Deblochează"</string>
     <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sunet activat"</string>
@@ -1017,7 +1017,7 @@
     <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</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âneți în 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>
@@ -1052,7 +1052,7 @@
     <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"</string>
     <string name="save_password_never" msgid="6776808375903410659">"Niciodată"</string>
@@ -1081,7 +1081,7 @@
     <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_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>
@@ -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,7 +1138,7 @@
     <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>
@@ -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>
@@ -1192,13 +1192,13 @@
     <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="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>
@@ -1219,7 +1219,7 @@
     <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="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>
@@ -1249,11 +1249,11 @@
     <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_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>
@@ -1268,8 +1268,8 @@
     <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 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 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_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>
@@ -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>
@@ -1326,18 +1326,18 @@
     <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_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 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">"Activează 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" 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>
@@ -1371,19 +1371,19 @@
     <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">"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,7 +1394,7 @@
     <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_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>
@@ -1403,28 +1403,28 @@
     <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_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">"Reintrodu dispozitivul"</string>
@@ -1466,10 +1466,10 @@
     <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>
@@ -1507,12 +1507,12 @@
     <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">"Distribuie"</string>
-    <string name="find" msgid="5015737188624767706">"Găsiți"</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_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">"Creșteți valoarea pentru minute"</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">"Creșteți valoarea pentru oră"</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">"Reduceți 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">"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="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,7 +1558,7 @@
     <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="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>
@@ -1642,59 +1642,59 @@
     <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">"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_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">"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">"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 prestabilite din fabrică, iar toate datele de utilizator se vor pierde."</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">"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 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_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">"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_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">"Elimină"</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="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ă 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="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">"Activezi 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_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ți"</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">"Editează comenzile rapide"</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>
@@ -1723,7 +1723,7 @@
     <string name="error_message_title" msgid="4082495589294631966">"Eroare"</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>
@@ -1831,12 +1831,12 @@
     <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">"Confirmă 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_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>
@@ -1854,7 +1854,7 @@
     <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 administrator"</string>
     <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administrator"</string>
-    <string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administratorul dvs."</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>
@@ -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>
@@ -1905,13 +1905,13 @@
     <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"</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>
@@ -1937,7 +1937,7 @@
     <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">"Activezi 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_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>
@@ -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_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 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>
@@ -1995,7 +1995,7 @@
     <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_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>
@@ -2024,9 +2024,9 @@
     <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"</string>
     <string name="etws_primary_default_message_others" msgid="7958161706019130739"></string>
@@ -2045,13 +2045,14 @@
     <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"</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_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">"Editează"</string>
@@ -2060,11 +2061,11 @@
     <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>
@@ -2082,7 +2083,7 @@
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</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>
@@ -2270,9 +2271,9 @@
     <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="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">"Respingeți"</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>
@@ -2280,8 +2281,8 @@
     <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 bad041a..607da13 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -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 750c16c..e63e2ac 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -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 060f440..061eb48 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3022,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>
@@ -3325,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
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b060593..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>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c3d4088..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" />
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/text/LayoutGetRangeForRectTest.java b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
index 32fdb5e..787a405 100644
--- a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
+++ b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
@@ -90,7 +90,8 @@
 
         mLineCenters = new float[mLayout.getLineCount()];
         for (int i = 0; i < mLayout.getLineCount(); ++i) {
-            mLineCenters[i] = (mLayout.getLineTop(i) + mLayout.getLineBottomWithoutSpacing(i)) / 2f;
+            mLineCenters[i] = (mLayout.getLineTop(i)
+                    + mLayout.getLineBottom(i, /* includeLineSpacing= */ false)) / 2f;
         }
 
         mGraphemeClusterSegmentIterator =
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/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/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 352f81b..ba95378 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -24,13 +24,13 @@
     <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_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">"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="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>
@@ -49,9 +49,9 @@
     <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="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">"Părăsiți 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">"Adaugă înapoi în stivă"</string>
@@ -63,27 +63,27 @@
     <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 balonul"</string>
-    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișați conversația în balon"</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">"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>
+    <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 pentru mai multe informații"</string>
-    <string name="maximize_button_text" msgid="1650859196290301963">"Maximizați"</string>
+    <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
 </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/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/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 1c0e6f7..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
@@ -20,6 +20,9 @@
 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;
 import android.content.Context;
@@ -42,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 {
@@ -85,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());
@@ -129,7 +131,8 @@
      */
     @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.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -145,23 +148,25 @@
             return createChangeAnimationAdapters(info, startTransaction);
         }
         if (Transitions.isClosingType(info.getType())) {
-            return createCloseAnimationAdapters(info);
+            return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
         }
-        return createOpenAnimationAdapters(info);
+        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);
     }
 
     /**
@@ -170,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<>();
@@ -194,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++);
             }
@@ -202,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++);
             }
@@ -213,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);
     }
@@ -322,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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 33074de..c25bbbf 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
@@ -29,6 +29,7 @@
 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;
@@ -57,6 +58,8 @@
 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;
@@ -171,6 +174,7 @@
     @WMSingleton
     @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(
+            Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             CompatUIController compatUI,
@@ -178,6 +182,10 @@
             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);
     }
@@ -482,12 +490,14 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, desktopModeTaskRepository, mainExecutor));
+                        taskStackListener, activityTaskManager, desktopModeTaskRepository,
+                        mainExecutor));
     }
 
     //
@@ -716,15 +726,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();
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..37a50b6 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;
@@ -189,14 +188,16 @@
             @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);
     }
 
     //
@@ -319,6 +320,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -338,11 +340,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 +598,18 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeController> provideDesktopModeController(
-            Context context, ShellInit shellInit,
+    @DynamicOverride
+    static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
             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, shellTaskOrganizer,
+                rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
+                mainExecutor);
     }
 
     @WMSingleton
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..ff3be38 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,16 @@
 
 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 {
+@ExternalThread
+public interface 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;
-        }
+    /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
+    default IDesktopMode createExternalInterface() {
+        return null;
     }
 }
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..99739c4 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
@@ -20,8 +20,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -29,51 +31,83 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.BinderThread;
 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.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.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> {
 
     private final Context mContext;
     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 DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+    private final SettingsObserver mSettingsObserver;
 
     public DesktopModeController(Context context, ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            Transitions transitions,
+            DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread Handler mainHandler,
-            Transitions transitions) {
+            @ShellMainThread ShellExecutor mainExecutor) {
         mContext = context;
         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");
         mSettingsObserver.observe();
-        if (DesktopMode.isActive(mContext)) {
+        if (DesktopModeStatus.isActive(mContext)) {
             updateDesktopModeActive(true);
         }
     }
 
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
+    }
+
+    /**
+     * Get connection interface between sysui and shell
+     */
+    public DesktopMode asDesktopMode() {
+        return mDesktopModeImpl;
+    }
+
     @VisibleForTesting
     void updateDesktopModeActive(boolean active) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -121,6 +155,47 @@
     }
 
     /**
+     * Show apps on desktop
+     */
+    public void 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);
+        }
+        mShellTaskOrganizer.applyTransaction(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();
+    }
+
+    /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
     private final class SettingsObserver extends ContentObserver {
@@ -150,8 +225,51 @@
         }
 
         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 {
+
+        private IDesktopModeImpl mIDesktopMode;
+
+        @Override
+        public IDesktopMode createExternalInterface() {
+            if (mIDesktopMode != null) {
+                mIDesktopMode.invalidate();
+            }
+            mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
+            return mIDesktopMode;
+        }
+    }
+
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private static class IDesktopModeImpl extends IDesktopMode.Stub {
+
+        private DesktopModeController mController;
+
+        IDesktopModeImpl(DesktopModeController controller) {
+            mController = controller;
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        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/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
similarity index 65%
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/desktopmode/IDesktopMode.aidl
index 44c0496..5042bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.wm.shell.desktopmode;
 
 /**
- * Provides information on the current wifi activity.
+ * Interface that is exposed to remote callers to manipulate desktop mode features.
  */
-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,
-)
+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/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index f58719b..e2d5a49 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
@@ -28,7 +28,7 @@
 
 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;
@@ -90,7 +90,7 @@
             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));
@@ -123,7 +123,7 @@
                 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));
@@ -150,7 +150,7 @@
             mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
         }
 
-        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);
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/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bc8191d..af47666 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
@@ -130,6 +130,7 @@
     private DisplayController mDisplayController;
     private PipInputConsumer mPipInputConsumer;
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
+    private PipAnimationController mPipAnimationController;
     private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -158,7 +159,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 +369,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -392,11 +394,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 +408,7 @@
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
+            PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -445,6 +449,7 @@
         mMediaController = pipMediaController;
         mMenuController = phonePipMenuController;
         mTouchHandler = pipTouchHandler;
+        mPipAnimationController = pipAnimationController;
         mAppOpsListener = pipAppOpsListener;
         mOneHandedController = oneHandedController;
         mPipTransitionController = pipTransitionController;
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/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index a5748f6..2a62552 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.
@@ -29,4 +34,11 @@
     default IRecentTasks createExternalInterface() {
         return null;
     }
+
+    /**
+     * Gets the set of recent tasks.
+     */
+    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..02b5a35 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
@@ -24,6 +24,7 @@
 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;
@@ -43,7 +44,7 @@
 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;
@@ -57,6 +58,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.
@@ -71,6 +74,7 @@
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasks mImpl = new RecentTasksImpl();
+    private final ActivityTaskManager mActivityTaskManager;
     private IRecentTasksListener mListener;
     private final boolean mIsDesktopMode;
 
@@ -95,6 +99,7 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
@@ -102,17 +107,19 @@
             return null;
         }
         return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                desktopModeTaskRepository, mainExecutor);
+                activityTaskManager, desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
+        mActivityTaskManager = activityTaskManager;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -269,15 +276,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 +288,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 +321,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 +328,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);
@@ -352,6 +376,16 @@
             mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
             return mIRecentTasks;
         }
+
+        @Override
+        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));
+            });
+        }
     }
 
 
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..07a6895 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
@@ -385,6 +385,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 +475,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");
         }
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/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/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e8a2cb160..9e49b51 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;
@@ -36,7 +37,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;
 
@@ -53,6 +55,7 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
+    private DesktopModeController mDesktopModeController;
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -60,7 +63,8 @@
             Choreographer mainChoreographer,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            DesktopModeController desktopModeController) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -68,6 +72,7 @@
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
+        mDesktopModeController = desktopModeController;
     }
 
     @Override
@@ -210,8 +215,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 +236,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 +252,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/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..6d13377 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
@@ -20,7 +20,6 @@
 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
@@ -46,6 +45,66 @@
     }
 }
 
+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)
 }
@@ -113,25 +172,12 @@
     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)
     }
 }
 
@@ -271,6 +317,14 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsVisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowVisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowIsVisibleAtEnd(
     component: IComponentMatcher
 ) {
@@ -279,6 +333,22 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsInvisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowInvisible(component)
+    }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtEnd(
+    component: IComponentMatcher
+) {
+    assertWmEnd {
+        this.isAppWindowInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(
     component: IComponentMatcher
 ) {
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 7dbd279..5b044e3 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
@@ -27,9 +27,13 @@
 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.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
@@ -64,36 +68,44 @@
     @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)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     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)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
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 3646fd7..838026f 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
@@ -33,6 +33,7 @@
 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.FixMethodOrder
 import org.junit.Test
@@ -75,25 +76,25 @@
     @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() {
@@ -116,12 +117,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @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 80abedd..a764206 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
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 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
@@ -64,13 +65,15 @@
                     .waitForAndVerify()
             }
         }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
@@ -86,18 +89,15 @@
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         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)
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 2915787..ba02317 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
@@ -28,9 +28,13 @@
 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.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
@@ -63,14 +67,27 @@
     @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() {
@@ -83,23 +100,19 @@
         }
     }
 
-    @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
     @Test
     fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
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 8e041a7..facbcab 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
@@ -35,6 +35,7 @@
 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
@@ -82,13 +83,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() {
@@ -98,53 +102,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 047bcfe..e208196 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,8 +21,6 @@
 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
@@ -36,6 +34,7 @@
 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
@@ -73,12 +72,7 @@
             setup {
                 // Send a notification
                 sendNotificationApp.launchViaIntent(wmHelper)
-                val sendNotification = device.wait(
-                    Until.findObject(By.text("Send Notification")),
-                    SplitScreenUtils.TIMEOUT_MS
-                )
-                sendNotification?.click() ?: error("Send notification button not found")
-
+                sendNotificationApp.postNotification(wmHelper)
                 tapl.goHome()
                 primaryApp.launchViaIntent(wmHelper)
             }
@@ -94,13 +88,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() {
@@ -110,12 +107,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -139,24 +134,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 a11874e..84d2e6a 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
@@ -35,6 +35,7 @@
 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
@@ -85,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() {
@@ -101,12 +105,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -130,24 +132,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 6064b52..23623aa 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
@@ -31,6 +31,7 @@
 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.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,36 +72,34 @@
     @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)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(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/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 96b9faa..e57ac91 100644
--- 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
@@ -41,7 +41,7 @@
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 
 internal object SplitScreenUtils {
-    internal const val TIMEOUT_MS = 3_000L
+    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"
@@ -52,7 +52,7 @@
     private val notificationScrollerSelector: BySelector
         get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
     private val notificationContentSelector: BySelector
-        get() = By.text("Notification content")
+        get() = By.text("Flicker Test Notification")
     private val dividerBarSelector: BySelector
         get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
 
@@ -140,14 +140,14 @@
         // Pull down the notifications
         device.swipe(
             displayBounds.centerX(), 5,
-            displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
+            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) {
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 f06dd66f6..025bb408 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
@@ -31,9 +31,12 @@
 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.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
@@ -111,19 +114,31 @@
     @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.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -136,12 +151,10 @@
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(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/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 5c30116..9947a53 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
@@ -30,6 +30,7 @@
 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
@@ -69,36 +70,34 @@
     @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 primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(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 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)
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 9c66a37..3716dc9 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
@@ -30,6 +30,7 @@
 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
@@ -68,36 +69,34 @@
     @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 primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(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 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)
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 e8862bd..db07f21 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
@@ -30,6 +30,7 @@
 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
@@ -70,36 +71,34 @@
     @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 primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(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 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)
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/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index c628f399..dd23d97 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,17 @@
 
 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.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;
@@ -67,18 +77,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() {
+        mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isActive(any())).thenReturn(true);
+
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+        mExecutor = new TestShellExecutor();
+
+        mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
         mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
-                mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
+                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 +189,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 +205,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/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index a8d3bdc..1e08f1e 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
@@ -48,6 +48,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;
@@ -85,6 +86,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 +119,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,
@@ -183,8 +185,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..b8aaaa7 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
@@ -40,6 +40,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,9 +53,8 @@
 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.ShellInit;
@@ -68,7 +68,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}.
@@ -85,11 +87,13 @@
     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 +101,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));
+                mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+                Optional.of(mDesktopModeTaskRepository), mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
                 mMainExecutor);
@@ -188,10 +192,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 +331,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 +342,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/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/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/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/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/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/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/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/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/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-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-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-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-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 e43bed5..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>
@@ -41,8 +41,8 @@
     <string name="consent_yes" msgid="8344487259618762872">"Permite"</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-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/DynamicSystemInstallationService/res/values-ro/strings.xml b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
index 22a46d5..a8a5125 100644
--- a/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-ro/strings.xml
@@ -1,14 +1,14 @@
 <?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">"Introdu 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_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țați"</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>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index 6b793fc..9ca4543 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -24,8 +24,8 @@
     <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>
@@ -56,7 +56,7 @@
     <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">"Doriți să dezinstalați această aplicație din profilul de serviciu?"</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>
@@ -81,9 +81,9 @@
     <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="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>
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 89%
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 6fe88e1..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
@@ -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/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 c698d9c..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
@@ -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 d923c1c..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.
@@ -42,10 +42,9 @@
  *   $ 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.
@@ -220,7 +219,7 @@
     }
 
     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 {
@@ -231,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/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
new file mode 100644
index 0000000..111555b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.app.Activity
+
+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/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/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/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
deleted file mode 100644
index 99a08ab..0000000
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.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.settingslib.spaprivileged.model.app
-
-import android.content.pm.ApplicationInfo
-import android.os.UserHandle
-
-val ApplicationInfo.userId: Int
-    get() = UserHandle.getUserId(uid)
-
-val ApplicationInfo.userHandle: UserHandle
-    get() = UserHandle.getUserHandleForUid(uid)
-
-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 883eddf..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
@@ -128,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()
         }
@@ -141,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/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
new file mode 100644
index 0000000..940a1fe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SpaPrivilegedLibTests",
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    srcs: [
+        ":SpaPrivilegedLib_srcs",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "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=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/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/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..3c75abf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -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.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 static final String DATABASE_NAME = "mobilenetworksettings_db";
+
+    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.databaseBuilder(context,
+                        MobileNetworkDatabase.class, DATABASE_NAME)
+                .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/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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6c17036..6fe8087 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -705,6 +705,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/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 5d876ea..2117191 100644
--- a/packages/SimAppDialog/res/values-ro/strings.xml
+++ b/packages/SimAppDialog/res/values-ro/strings.xml
@@ -19,8 +19,8 @@
     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">"Activează 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_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">"Descarcă aplicația"</string>
 </resources>
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/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/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index cad7159..5ee67d91 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -68,9 +68,9 @@
     <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{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_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 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_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">"Schimbă metoda de introducere"</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-product/values-ro/strings.xml b/packages/SystemUI/res-product/values-ro/strings.xml
index 807ebfe0..471f01e 100644
--- a/packages/SystemUI/res-product/values-ro/strings.xml
+++ b/packages/SystemUI/res-product/values-ro/strings.xml
@@ -19,16 +19,16 @@
 
 <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="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="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">"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="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>
@@ -38,8 +38,8 @@
     <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">"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="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>
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/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/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/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/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/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 6d12485..85278dd 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
@@ -62,6 +62,8 @@
     // 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";
+    // See IDesktopMode.aidl
+    public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
 
     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 b78fa9a..71470e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -57,11 +57,10 @@
     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/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4b6177a..f259a54 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2579,11 +2579,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 && !mBouncerFullyShown;
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+                && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
         final boolean isLockDown =
@@ -2623,16 +2620,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 && !mGoingToSleep
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
-                        || awakeKeyguardExcludingBouncerShowing
+                        || awakeKeyguard
                         || shouldListenForFaceAssistant
                         || mAuthController.isUdfpsFingerDown()
                         || mUdfpsBouncerShowing)
@@ -2658,11 +2654,10 @@
                     isFaceLockedOut(),
                     fpLockedout,
                     mGoingToSleep,
-                    awakeKeyguardExcludingBouncerShowing,
+                    awakeKeyguard,
                     mKeyguardGoingAway,
                     shouldListenForFaceAssistant,
                     mOccludingAppRequestingFace,
-                    onlyFaceEnrolled,
                     mIsPrimaryUser,
                     strongAuthAllowsScanning,
                     mSecureCameraLaunched,
@@ -2672,11 +2667,6 @@
         return shouldListen;
     }
 
-    private boolean isOnlyFaceEnrolled() {
-        return isFaceEnrolled()
-                && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
-    }
-
     private void maybeLogListenerModelData(KeyguardListenModel model) {
         mLogger.logKeyguardListenerModel(model);
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 67b683e..2e13903 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -455,6 +455,7 @@
                     }
                 }
 
+                boolean needToUpdateProviderViews = false;
                 final String newUniqueId = mDisplayInfo.uniqueId;
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
@@ -472,6 +473,37 @@
                         setupDecorations();
                         return;
                     }
+
+                    if (mScreenDecorHwcLayer != null) {
+                        updateHwLayerRoundedCornerDrawable();
+                        updateHwLayerRoundedCornerExistAndSize();
+                    }
+                    needToUpdateProviderViews = true;
+                }
+
+                final float newRatio = getPhysicalPixelDisplaySizeRatio();
+                if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
+                    mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
+                    if (mScreenDecorHwcLayer != null) {
+                        updateHwLayerRoundedCornerExistAndSize();
+                    }
+                    needToUpdateProviderViews = true;
+                }
+
+                if (needToUpdateProviderViews) {
+                    updateOverlayProviderViews(null);
+                } else {
+                    updateOverlayProviderViews(new Integer[] {
+                            mFaceScanningViewId,
+                            R.id.display_cutout,
+                            R.id.display_cutout_left,
+                            R.id.display_cutout_right,
+                            R.id.display_cutout_bottom,
+                    });
+                }
+
+                if (mScreenDecorHwcLayer != null) {
+                    mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
                 }
             }
         };
@@ -1037,8 +1069,6 @@
                 && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
             mRotation = newRotation;
             mDisplayMode = newMod;
-            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
-                    getPhysicalPixelDisplaySizeRatio());
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingConfigChange = false;
                 mScreenDecorHwcLayer.updateRotation(mRotation);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 50c38e5..a21f45f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -97,7 +97,8 @@
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
                     .setBackAnimation(mWMComponent.getBackAnimation())
-                    .setFloatingTasks(mWMComponent.getFloatingTasks());
+                    .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
@@ -117,7 +118,8 @@
                     .setStartingSurface(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
                     .setBackAnimation(Optional.ofNullable(null))
-                    .setFloatingTasks(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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb7..00236f1 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 {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 27e9af9..412dc05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -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/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/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/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 7e30431..0d06c51 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -40,6 +40,7 @@
 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;
@@ -113,6 +114,9 @@
         @BindsInstance
         Builder setFloatingTasks(Optional<FloatingTasks> f);
 
+        @BindsInstance
+        Builder setDesktopMode(Optional<DesktopMode> d);
+
         SysUIComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index dd11549..096f969 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -30,6 +30,7 @@
 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;
@@ -112,4 +113,10 @@
 
     @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/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index 8b4aeef..a252864 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,18 +78,23 @@
         reloadMeasures()
     }
 
+    private fun reloadAll(newReloadToken: Int) {
+        if (reloadToken == newReloadToken) {
+            return
+        }
+        reloadToken = newReloadToken
+        reloadRes()
+        reloadMeasures()
+    }
+
     fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
         if (displayUniqueId != newDisplayUniqueId) {
             displayUniqueId = newDisplayUniqueId
             newReloadToken ?.let { reloadToken = it }
             reloadRes()
             reloadMeasures()
-        } else if (newReloadToken != null) {
-            if (reloadToken == newReloadToken) {
-                return
-            }
-            reloadToken = newReloadToken
-            reloadMeasures()
+        } else {
+            newReloadToken?.let { reloadAll(it) }
         }
     }
 
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/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 223d79a..38d9d021 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -101,8 +101,8 @@
     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 new implementation of UserSwitcherController should be used. */
     public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
@@ -200,7 +200,8 @@
     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 =
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/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 29e2c1c..f2f6bad 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -334,4 +334,14 @@
     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);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
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/log/dagger/UdfpsLog.java
index 44c0496..14000e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UdfpsLog.java
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.log.dagger;
 
-/**
- * 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 static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+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/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index b516689..a9e1a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -516,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/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/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 59c6635..2b381a9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -42,7 +42,7 @@
     }
 
     private fun List<RecentTask>.sortTasks(): List<RecentTask> =
-        asReversed().sortedBy {
+        sortedBy {
             // Show normal tasks first and only then tasks with opened app selector
             it.topActivityComponent == appSelectorComponentName
         }
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
index 0bdddfe..0927f3b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -17,11 +17,17 @@
 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
-import javax.inject.Inject
 
 interface AppIconLoader {
     suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
@@ -31,11 +37,20 @@
 @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) {
-            // TODO(b/240924731): add a blocking call to load an icon using iconloaderlib
-            null
+            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/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 6d67e28..cd994b8 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.mediaprojection.appselector.data
 
+import android.annotation.ColorInt
 import android.content.ComponentName
 
 data class RecentTask(
     val taskId: Int,
     val userId: Int,
     val topActivityComponent: ComponentName?,
-    val baseIntentComponent: 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
index 5a09435..e8b49cd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,23 +16,61 @@
 
 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 javax.inject.Inject
+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) :
-    RecentTaskListProvider {
+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) {
-            // TODO(b/240924731): add blocking call to load the recents
-            emptyList()
+            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
index 4291280..47faaed 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -18,6 +18,7 @@
 
 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
@@ -30,12 +31,13 @@
 @Inject
 constructor(
     @Background private val coroutineDispatcher: CoroutineDispatcher,
-) :
-    RecentTaskThumbnailLoader {
+    private val activityManager: ActivityManagerWrapper
+) : RecentTaskThumbnailLoader {
 
     override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
         withContext(coroutineDispatcher) {
-            // TODO(b/240924731): add blocking call to load a thumbnail
-             null
+            val thumbnailData =
+                activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
+            if (thumbnailData.thumbnail == null) null else thumbnailData
         }
 }
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..0f1338e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -303,6 +303,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 =
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..24efc76 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -880,6 +880,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/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f41b905..18bd6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,7 +27,6 @@
 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;
@@ -76,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;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index a5c60a4..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;
 
@@ -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
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 7ce0ad0..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;
@@ -56,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;
     }
 
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/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 95edb35..7e2a5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -27,6 +27,7 @@
 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_DESKTOP_MODE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
 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;
@@ -110,6 +111,7 @@
 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.desktopmode.DesktopMode;
 import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
@@ -169,6 +171,7 @@
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
     private final Optional<RecentTasks> mRecentTasks;
     private final Optional<BackAnimation> mBackAnimation;
+    private final Optional<DesktopMode> mDesktopModeOptional;
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
@@ -488,6 +491,9 @@
             mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
                     KEY_EXTRA_SHELL_BACK_ANIMATION,
                     backAnimation.createExternalInterface().asBinder()));
+            mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
+                    KEY_EXTRA_SHELL_DESKTOP_MODE,
+                    desktopMode.createExternalInterface().asBinder())));
 
             try {
                 Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -573,6 +579,7 @@
             Optional<RecentTasks> recentTasks,
             Optional<BackAnimation> backAnimation,
             Optional<StartingSurface> startingSurface,
+            Optional<DesktopMode> desktopModeOptional,
             BroadcastDispatcher broadcastDispatcher,
             ShellTransitions shellTransitions,
             ScreenLifecycle screenLifecycle,
@@ -607,6 +614,7 @@
         mShellTransitions = shellTransitions;
         mRecentTasks = recentTasks;
         mBackAnimation = backAnimation;
+        mDesktopModeOptional = desktopModeOptional;
         mUiEventLogger = uiEventLogger;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
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/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/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..48c6e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -59,7 +59,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;
 
@@ -271,7 +272,7 @@
     }
 
     @Override
-    public void setVisibleState(int state, boolean animate) {
+    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
         if (state == mVisibleState) {
             return;
         }
@@ -312,6 +313,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..f3e74d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1eafaf0..f7ce43b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -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 */);
             }
         }
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..0026b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -352,8 +352,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);
         }
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/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index bd99713..d6d021f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -56,7 +56,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 public interface StatusBarIconController {
 
@@ -139,13 +138,15 @@
 
         public DarkIconManager(
                 LinearLayout linearLayout,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
                     mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
@@ -204,27 +205,28 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
             private final DarkIconDispatcher mDarkIconDispatcher;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider,
+                    WifiViewModel wifiViewModel,
                     MobileContextProvider mobileContextProvider,
                     DarkIconDispatcher darkIconDispatcher) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModelProvider = wifiViewModelProvider;
+                mWifiViewModel = wifiViewModel;
                 mMobileContextProvider = mobileContextProvider;
                 mDarkIconDispatcher = darkIconDispatcher;
             }
 
-            public DarkIconManager create(LinearLayout group) {
+            public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
                 return new DarkIconManager(
                         group,
+                        location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModelProvider,
+                        mWifiViewModel,
                         mMobileContextProvider,
                         mDarkIconDispatcher);
             }
@@ -239,12 +241,14 @@
 
         public TintedIconManager(
                 ViewGroup group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
                 MobileContextProvider mobileContextProvider) {
             super(group,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
                     mobileContextProvider);
         }
 
@@ -278,24 +282,25 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider,
+                    WifiViewModel wifiViewModel,
                     MobileContextProvider mobileContextProvider) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModelProvider = wifiViewModelProvider;
+                mWifiViewModel = wifiViewModel;
                 mMobileContextProvider = mobileContextProvider;
             }
 
-            public TintedIconManager create(ViewGroup group) {
+            public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
                 return new TintedIconManager(
                         group,
+                        location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModelProvider,
+                        mWifiViewModel,
                         mMobileContextProvider);
             }
         }
@@ -306,8 +311,9 @@
      */
     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;
         protected final Context mContext;
         protected final int mIconSize;
@@ -324,12 +330,14 @@
 
         public IconManager(
                 ViewGroup group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
                 MobileContextProvider mobileContextProvider) {
             mGroup = group;
+            mLocation = location;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
-            mWifiViewModelProvider = wifiViewModelProvider;
+            mWifiViewModel = wifiViewModel;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -446,7 +454,7 @@
 
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
             return ModernStatusBarWifiView.constructAndBind(
-                    mContext, slot, mWifiViewModelProvider.get());
+                    mContext, slot, mWifiViewModel, mLocation);
         }
 
         private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
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 a8ad564..04aa3a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -204,6 +204,7 @@
     protected CentralSurfaces mCentralSurfaces;
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
+    private boolean mCentralSurfacesRegistered;
 
     private View mNotificationContainer;
 
@@ -338,6 +339,7 @@
         mNotificationContainer = notificationContainer;
         mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
                 centralSurfaces.getKeyguardMessageArea());
+        mCentralSurfacesRegistered = true;
 
         registerListeners();
     }
@@ -730,7 +732,9 @@
     private void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            reset(true /* hideBouncerWhenShowing */);
+            if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+                reset(dozing /* hideBouncerWhenShowing */);
+            }
             updateStates();
 
             if (!dozing) {
@@ -1086,6 +1090,9 @@
     };
 
     protected void updateStates() {
+        if (!mCentralSurfacesRegistered) {
+            return;
+        }
         boolean showing = mShowing;
         boolean occluded = mOccluded;
         boolean bouncerShowing = bouncerIsShowing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.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/statusbar/phone/StatusBarLocation.kt
index 44c0496..5ace226 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.phone
 
-/**
- * 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,
-)
+/** 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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 891f657..b8bdc7d 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
@@ -64,6 +64,7 @@
 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;
@@ -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);
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..6c616ac 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.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
 
@@ -36,6 +40,17 @@
 ) : BaseStatusBarWifiView(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/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 485a7e5..aca60c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -86,13 +86,12 @@
     becauseCannotSkipBouncer = false,
     biometricSettingEnabledForUser = false,
     bouncerFullyShown = 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/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e7e3f34..12d3d42 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -213,8 +213,6 @@
             mBiometricEnabledCallbackArgCaptor;
     @Captor
     private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor;
 
     // Direct executor
     private final Executor mBackgroundExecutor = Runnable::run;
@@ -597,13 +595,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
@@ -1238,9 +1234,7 @@
     public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
             throws RemoteException {
         // Face auth should run when the following is true.
-        faceAuthEnabled();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         keyguardNotGoingAway();
         currentUserIsPrimary();
         strongAuthNotRequired();
@@ -1267,7 +1261,7 @@
         mKeyguardUpdateMonitor =
                 new TestableKeyguardUpdateMonitor(mSpiedContext);
 
-        // Preconditions for face auth to run
+        // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         strongAuthNotRequired();
@@ -1284,7 +1278,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 +1299,8 @@
     @Test
     public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1329,11 +1320,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 +1341,8 @@
     @Test
     public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
             throws RemoteException {
-        // Preconditions for face auth to run
-        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1375,7 +1361,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 +1384,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 +1396,6 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
 
         bouncerFullyVisibleAndNotGoingToSleep();
-        fingerprintIsNotEnrolled();
         mTestableLooper.processAllMessages();
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1420,7 +1404,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 +1430,6 @@
         biometricsNotDisabledThroughDevicePolicyManager();
         biometricsEnabledForCurrentUser();
         userNotCurrentlySwitching();
-        bouncerFullyVisible();
 
         statusBarShadeIsLocked();
         mTestableLooper.processAllMessages();
@@ -1460,9 +1443,6 @@
         keyguardIsVisible();
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
         statusBarShadeIsNotLocked();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
-        bouncerNotFullyVisible();
-
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
     }
 
@@ -1524,44 +1504,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();
-
-        bouncerFullyVisible();
-        mTestableLooper.processAllMessages();
-
-        assertThat(cancelSignal.isCanceled()).isTrue();
-    }
-
-    @Test
     public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
@@ -1624,21 +1566,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);
     }
@@ -1745,10 +1672,6 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
     }
 
-    private void bouncerNotFullyVisible() {
-        setKeyguardBouncerVisibility(false);
-    }
-
     private void bouncerFullyVisible() {
         setKeyguardBouncerVisibility(true);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 5a26d05..df10dfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1005,13 +1005,18 @@
         assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
         assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
 
-        doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
+        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
+                /* roundedTopDrawable */,
+                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
+                /* roundedBottomDrawable */,
+                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
         mScreenDecorations.onConfigurationChanged(null);
 
-        assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
-        assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
+        assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
+        assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
     }
 
     @Test
@@ -1288,6 +1293,51 @@
     }
 
     @Test
+    public void testOnDisplayChanged_hwcLayer() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+        decorationSupport.format = PixelFormat.R_8;
+        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+        // top cutout
+        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
+
+        mScreenDecorations.start();
+
+        final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
+        spyOn(hwcLayer);
+        doReturn(mDisplay).when(hwcLayer).getDisplay();
+
+        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+        verify(hwcLayer, times(1)).onDisplayChanged(any());
+    }
+
+    @Test
+    public void testOnDisplayChanged_nonHwcLayer() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+        // top cutout
+        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
+
+        mScreenDecorations.start();
+
+        final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
+                mScreenDecorations.getOverlayView(R.id.display_cutout);
+        assertNotNull(cutoutView);
+        spyOn(cutoutView);
+        doReturn(mDisplay).when(cutoutView).getDisplay();
+
+        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+        verify(cutoutView, times(1)).onDisplayChanged(any());
+    }
+
+    @Test
     public void testHasSameProvidersWithNullOverlays() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
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..53e30fd 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;
 
@@ -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/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/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index 93a1868..f933361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,11 +24,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R as InternalR
 import com.android.systemui.R as SystemUIR
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.tests.R
+import com.android.systemui.SysuiTestCase
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
+
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
@@ -101,11 +102,14 @@
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
 
-        roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
+        setupResources(radius = 100,
+                roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
+                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
+
         roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
 
-        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
+        assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
     }
 
     @Test
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/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/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/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/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 37b7f47..00b1f32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -54,7 +54,7 @@
     }
 
     @Test
-    fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInReverse() {
+    fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
         val tasks = listOf(
             createRecentTask(taskId = 1),
             createRecentTask(taskId = 2),
@@ -66,15 +66,15 @@
 
         verify(view).bind(
             listOf(
-                createRecentTask(taskId = 3),
-                createRecentTask(taskId = 2),
                 createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
             )
         )
     }
 
     @Test
-    fun initRecentTasksWithAppSelectorTasks_bindsListInReverseAndAppSelectorTasksAtTheEnd() {
+    fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
         val tasks = listOf(
             createRecentTask(taskId = 1),
             createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
@@ -88,11 +88,11 @@
 
         verify(view).bind(
             listOf(
-                createRecentTask(taskId = 5),
-                createRecentTask(taskId = 3),
                 createRecentTask(taskId = 1),
-                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 5),
                 createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
             )
         )
     }
@@ -105,7 +105,8 @@
             taskId = taskId,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
-            userId = 0
+            userId = 0,
+            colorBackground = 0
         )
     }
 
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/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index cbe1186..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;
@@ -253,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();
@@ -262,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 4af5b90..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()
     }
@@ -158,8 +155,7 @@
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
-        dumpManager: DumpManager,
-        mediaCarouselController: MediaCarouselController
+        dumpManager: DumpManager
     ) :
         QuickQSPanelController(
             view,
@@ -171,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/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/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/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/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index ba5f503..cfaa470 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
@@ -135,7 +135,7 @@
 
         MockitoAnnotations.initMocks(this);
 
-        when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
 
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
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..34399b8 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
@@ -51,8 +51,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import javax.inject.Provider;
-
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -79,8 +77,9 @@
         LinearLayout layout = new LinearLayout(mContext);
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
+                StatusBarLocation.HOME,
                 mock(StatusBarPipelineFlags.class),
-                () -> mock(WifiViewModel.class),
+                mock(WifiViewModel.class),
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -121,13 +120,15 @@
 
         TestDarkIconManager(
                 LinearLayout group,
+                StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider,
+                WifiViewModel wifiViewModel,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
+                    location,
                     statusBarPipelineFlags,
-                    wifiViewModelProvider,
+                    wifiViewModel,
                     contextProvider,
                     darkIconDispatcher);
         }
@@ -165,8 +166,9 @@
     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),
                     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 ee4b9d9c..dcce61b 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
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -526,21 +525,4 @@
         mBouncerExpansionCallback.onVisibilityChanged(false);
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
     }
-
-    @Test
-    public void testSetDozing_Dozing() {
-        clearInvocations(mBouncer);
-        mStatusBarKeyguardViewManager.onDozingChanged(true);
-        // Once when shown and once with dozing changed.
-        verify(mBouncer, times(1)).hide(false);
-    }
-
-    @Test
-    public void testSetDozing_notDozing() {
-        mStatusBarKeyguardViewManager.onDozingChanged(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..1ce4d61 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
@@ -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();
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/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/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml
index 94a7909..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>
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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8d94f95..f17f8f7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -731,7 +731,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 +748,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)) {
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/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/core/Android.bp b/services/core/Android.bp
index a4fc160..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
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index e3c8afa..b2abdbd 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 
+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 +50,13 @@
      * {@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
+     */
+    void setTimeZone(String tzId, @TimeZoneConfidence int confidence);
 }
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/OWNERS b/services/core/java/com/android/server/OWNERS
index 6ff8e36..5dbdb9b 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -35,6 +35,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/SystemTimeZone.java b/services/core/java/com/android/server/SystemTimeZone.java
new file mode 100644
index 0000000..7ce0831
--- /dev/null
+++ b/services/core/java/com/android/server/SystemTimeZone.java
@@ -0,0 +1,160 @@
+/*
+ * 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.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+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;
+
+    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)) {
+            Slog.w(TAG, TIME_ZONE_SYSTEM_PROPERTY + " is not valid (" + timezoneProperty
+                    + "); setting to " + DEFAULT_TIME_ZONE_ID);
+            setTimeZoneId(DEFAULT_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW);
+        }
+    }
+
+    /**
+     * Updates the device's time zone system property and associated metadata. 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
+     * com.android.server.AlarmManagerInternal#setTimeZone(String, int)} to ensure that important
+     * system-wide side effects occur.
+     */
+    public static boolean setTimeZoneId(String timeZoneId, @TimeZoneConfidence int confidence) {
+        if (TextUtils.isEmpty(timeZoneId) || !isValidTimeZoneId(timeZoneId)) {
+            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;
+            }
+            setTimeZoneConfidence(confidence);
+        }
+
+        return timeZoneChanged;
+    }
+
+    /**
+     * Sets the time zone confidence value if required. See {@link TimeZoneConfidence} for details.
+     */
+    private static void setTimeZoneConfidence(@TimeZoneConfidence int confidence) {
+        int currentConfidence = getTimeZoneConfidence();
+        if (currentConfidence != confidence) {
+            SystemProperties.set(
+                    TIME_ZONE_CONFIDENCE_SYSTEM_PROPERTY, Integer.toString(confidence));
+            if (DEBUG) {
+                Slog.v(TAG, "Time zone confidence changed: old=" + currentConfidence
+                        + ", new=" + confidence);
+            }
+        }
+    }
+
+    /** 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);
+    }
+
+    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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8b17909..1a0a222 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;
@@ -245,6 +246,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionInfo;
+import android.content.pm.PermissionMethod;
 import android.content.pm.ProcessInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ProviderInfoList;
@@ -257,6 +259,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 +334,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;
@@ -2365,6 +2369,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);
@@ -2479,6 +2484,7 @@
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
 
         mUserController = new UserController(this);
+        mInjector.mUserController = mUserController;
 
         mPendingIntentController = new PendingIntentController(
                 mHandlerThread.getLooper(), mUserController, mConstants);
@@ -4521,7 +4527,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);
         }
 
     }
@@ -4530,7 +4536,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;
     }
@@ -4666,7 +4672,7 @@
         if (doit) {
             for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
                 didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
-                        packageName, null, userId, doit);
+                        packageName, null, userId);
             }
         }
 
@@ -5969,6 +5975,12 @@
         }
     }
 
+    /**
+     * 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)}.
+     */
+    @PermissionMethod
     public static int checkComponentPermission(String permission, int pid, int uid,
             int owningUid, boolean exported) {
         if (pid == MY_PID) {
@@ -6015,6 +6027,7 @@
      * This can be called with or without the global lock held.
      */
     @Override
+    @PermissionMethod
     public int checkPermission(String permission, int pid, int uid) {
         if (permission == null) {
             return PackageManager.PERMISSION_DENIED;
@@ -6026,6 +6039,7 @@
      * Binder IPC calls go through the public entry point.
      * This can be called with or without the global lock held.
      */
+    @PermissionMethod
     int checkCallingPermission(String permission) {
         return checkPermission(permission,
                 Binder.getCallingPid(),
@@ -6035,6 +6049,7 @@
     /**
      * This can be called with or without the global lock held.
      */
+    @PermissionMethod
     void enforceCallingPermission(String permission, String func) {
         if (checkCallingPermission(permission)
                 == PackageManager.PERMISSION_GRANTED) {
@@ -6052,6 +6067,25 @@
     /**
      * This can be called with or without the global lock held.
      */
+    @PermissionMethod
+    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.
+     */
     void enforcePermission(String permission, int pid, int uid, String func) {
         if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
             return;
@@ -16328,8 +16362,34 @@
 
     @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);
+    }
+
+    @Override
+    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        enforceCallingHasAtLeastOnePermission("getSecondaryDisplayIdsForStartingBackgroundUsers()",
+                MANAGE_USERS, INTERACT_ACROSS_USERS);
+        return mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
     }
 
     /**
@@ -17361,6 +17421,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(),
@@ -18336,8 +18398,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;
@@ -18363,6 +18427,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..10e2aae 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;
         }
@@ -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/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 2ebe0b4..f9b0dd0 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
@@ -24,6 +25,8 @@
 import android.database.ContentObserver;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.KeyValueListParser;
 import android.util.Slog;
@@ -39,6 +42,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 +121,43 @@
     // started its process can start a background activity.
     public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
 
+    /**
+     * 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 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 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 int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Default 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 long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Default delay to apply to
+     * broadcasts targeting cached applications.
+     */
+    public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
+    private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -128,7 +171,7 @@
 
         @Override
         public void onChange(boolean selfChange) {
-            updateConstants();
+            updateSettingsConstants();
         }
     }
 
@@ -148,11 +191,15 @@
         mSettingsObserver = new SettingsObserver(handler);
         mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey),
                 false, mSettingsObserver);
+        updateSettingsConstants();
 
-        updateConstants();
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                new HandlerExecutor(handler), this::updateDeviceConfigConstants);
+        updateDeviceConfigConstants(
+                DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER));
     }
 
-    private void updateConstants() {
+    private void updateSettingsConstants() {
         synchronized (mParser) {
             try {
                 mParser.setString(Settings.Global.getString(mResolver, mSettingsKey));
@@ -173,6 +220,19 @@
         }
     }
 
+    private void updateDeviceConfigConstants(@NonNull DeviceConfig.Properties properties) {
+        MAX_RUNNING_PROCESS_QUEUES = properties.getInt("bcast_max_running_process_queues",
+                DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+        MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts",
+                DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+        MAX_PENDING_BROADCASTS = properties.getInt("bcast_max_pending_broadcasts",
+                DEFAULT_MAX_PENDING_BROADCASTS);
+        DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis",
+                DEFAULT_DELAY_NORMAL_MILLIS);
+        DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis",
+                DEFAULT_DELAY_CACHED_MILLIS);
+    }
+
     /**
      * Standard dumpsys support; invoked from BroadcastQueue dump
      */
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index f9fcc9e..1369435 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -16,18 +16,26 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.BroadcastQueue.checkState;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
+import android.content.pm.ResolveInfo;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
+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
@@ -40,21 +48,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.
  */
+// @NotThreadSafe
 class 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;
-
+    final @NonNull BroadcastConstants constants;
     final @NonNull String processName;
     final int uid;
 
@@ -78,6 +78,11 @@
     @Nullable ProcessRecord app;
 
     /**
+     * Track name to use for {@link Trace} events.
+     */
+    @Nullable String traceTrackName;
+
+    /**
      * 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.
@@ -102,6 +107,12 @@
     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;
@@ -109,12 +120,18 @@
     private int mCountAlarm;
 
     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;
     }
 
@@ -148,6 +165,51 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
+     * Remove any broadcasts matching the given predicate.
+     * <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 removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate,
+            @NonNull BroadcastConsumer consumer) {
+        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);
+                args.recycle();
+                it.remove();
+                didSomething = true;
+            }
+        }
+        // TODO: also check any active broadcast once we have a better "nonce"
+        // representing each scheduled broadcast to avoid races
+        return didSomething;
+    }
+
+    /**
      * Update if this process is in the "cached" state, typically signaling that
      * broadcast dispatch should be paused or delayed.
      */
@@ -187,16 +249,24 @@
         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--;
@@ -217,21 +287,55 @@
         mActive = null;
         mActiveIndex = 0;
         mActiveCountSinceIdle = 0;
+        mActiveViaColdStart = false;
+        invalidateRunnableAt();
     }
 
-    public void setActiveDeliveryState(int deliveryState) {
-        checkState(isActive(), "isActive");
-        mActive.setDeliveryState(mActiveIndex, deliveryState);
+    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() {
@@ -257,30 +361,100 @@
         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;
     }
 
+    private static final int REASON_EMPTY = 0;
+    private static final int REASON_CONTAINS_FOREGROUND = 1;
+    private static final int REASON_CONTAINS_ORDERED = 2;
+    private static final int REASON_CONTAINS_ALARM = 3;
+    private static final int REASON_CACHED = 4;
+    private static final int REASON_NORMAL = 5;
+    private static final int REASON_MAX_PENDING = 6;
+    private static final int REASON_BLOCKED_ORDERED = 7;
+
+    @IntDef(flag = false, prefix = { "REASON_" }, value = {
+            REASON_EMPTY,
+            REASON_CONTAINS_FOREGROUND,
+            REASON_CONTAINS_ORDERED,
+            REASON_CONTAINS_ALARM,
+            REASON_CACHED,
+            REASON_NORMAL,
+            REASON_MAX_PENDING,
+            REASON_BLOCKED_ORDERED,
+    })
+    @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_CACHED: return "CACHED";
+            case REASON_NORMAL: return "NORMAL";
+            case REASON_MAX_PENDING: return "MAX_PENDING";
+            case REASON_BLOCKED_ORDERED: return "BLOCKED_ORDERED";
+            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 long runnableAt = r.enqueueTime;
+
+            // If our next broadcast is ordered, and we're not the next receiver
+            // in line, then we're not runnable at all
+            if (r.ordered && r.finishedCount != index) {
+                mRunnableAt = Long.MAX_VALUE;
+                mRunnableAtReason = REASON_BLOCKED_ORDERED;
+                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 (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;
         }
     }
 
@@ -346,33 +520,68 @@
 
     @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) {
+    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));
+        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();
+    }
+
+    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 b1be022..d14fd16 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -60,6 +60,10 @@
         }
     }
 
+    static void logw(String msg) {
+        Slog.w(TAG, msg);
+    }
+
     static void logv(String msg) {
         Slog.v(TAG, msg);
     }
@@ -114,6 +118,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);
@@ -123,7 +130,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
@@ -131,14 +138,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
@@ -147,7 +154,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
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index a980db1..a515e5c 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;
@@ -233,6 +232,8 @@
     }
 
     public void enqueueBroadcastLocked(BroadcastRecord r) {
+        r.applySingletonPolicy(mService);
+
         final boolean replacePending = (r.intent.getFlags()
                 & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
 
@@ -427,16 +428,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) {
@@ -602,7 +603,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
@@ -733,9 +734,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;
@@ -815,7 +817,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) {
@@ -823,6 +829,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);
@@ -1231,6 +1238,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();
@@ -1380,8 +1388,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);
@@ -1447,7 +1458,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.
@@ -1509,19 +1520,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;
@@ -1693,18 +1697,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;
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8dfb22e..5154b5e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -19,11 +19,22 @@
 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.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.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;
@@ -32,24 +43,35 @@
 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.util.IndentingPrintWriter;
-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 java.io.FileDescriptor;
@@ -59,6 +81,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
 
 /**
  * Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
@@ -68,6 +91,21 @@
  * {@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,
@@ -80,39 +118,33 @@
             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.
+     * When enabled, invoke {@link #checkConsistencyLocked()} periodically to
+     * verify that our internal state is consistent. Checking consistency is
+     * relatively expensive, so this should be typically disabled.
      */
-    // TODO: shift hard-coded defaults to BroadcastConstants
-    private static final int MAX_RUNNING_PROCESS_QUEUES = 4;
-
-    /**
-     * 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.
-     */
-    // TODO: shift hard-coded defaults to BroadcastConstants
-    private static final int MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+    private static final boolean CHECK_CONSISTENCY = true;
 
     /**
      * Map from UID to per-process broadcast queues. If a UID hosts more than
@@ -136,11 +168,14 @@
     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
@@ -156,11 +191,13 @@
     @GuardedBy("mService")
     private final ArrayList<CountDownLatch> mWaitingForIdle = 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 = 2;
+    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -184,11 +221,44 @@
                 }
                 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;
+            }
         }
         return false;
     };
 
     /**
+     * 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>
@@ -198,7 +268,7 @@
      */
     @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;
@@ -215,9 +285,7 @@
                         ? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
                 final boolean nextHigher = (queue.runnableAtNext != null)
                         ? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
-                if (prevLower && nextHigher) {
-                    return;
-                } else {
+                if (!prevLower || !nextHigher) {
                     mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
                     mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
                 }
@@ -227,20 +295,28 @@
         } else if (inQueue) {
             mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
         }
+
+        // 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);
+        }
+
+        if (CHECK_CONSISTENCY) checkConsistencyLocked();
     }
 
     /**
      * 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();
+        int avail = mRunning.length - getRunningSize();
         if (avail == 0) return;
 
+        final int cookie = traceBegin(TAG, "updateRunningList");
         final long now = SystemClock.uptimeMillis();
 
         // If someone is waiting to go idle, everything is runnable now
@@ -285,23 +361,35 @@
                     + " from runnable to running; process is " + queue.app);
 
             // Allocate this available permit and start running!
-            mRunning.add(queue);
+            final int queueIndex = getRunningIndexOf(null);
+            mRunning[queueIndex] = queue;
             avail--;
 
             // Remove ourselves from linked list of runnable things
             mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
 
-            queue.makeActiveNextPending();
+            // Emit all trace events for this process into a consistent track
+            queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
 
-            // If we're already warm, schedule it; otherwise we'll wait for the
-            // cold start to circle back around
+            // 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
@@ -316,6 +404,10 @@
             mWaitingForIdle.forEach((latch) -> latch.countDown());
             mWaitingForIdle.clear();
         }
+
+        if (CHECK_CONSISTENCY) checkConsistencyLocked();
+
+        traceEnd(TAG, cookie);
     }
 
     @Override
@@ -324,9 +416,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;
@@ -335,18 +431,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
@@ -354,7 +449,6 @@
 
             // We might be willing to kick off another cold start
             enqueueUpdateRunningList();
-            didSomething = true;
         }
 
         final BroadcastProcessQueue queue = getProcessQueue(app);
@@ -364,17 +458,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.removeMatchingBroadcasts((r, i) -> {
+                return (r.receivers.get(i) instanceof BroadcastFilter);
+            }, mBroadcastConsumerSkip);
+            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;
@@ -382,8 +483,18 @@
 
     @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();
+            skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
+                // We only allow caller to clear broadcasts they enqueued
+                return (testRecord.callingUid == r.callingUid)
+                        && removeMatching.test(testRecord.intent);
+            });
+        }
 
         r.enqueueTime = SystemClock.uptimeMillis();
         r.enqueueRealTime = SystemClock.elapsedRealtime();
@@ -397,13 +508,34 @@
             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();
@@ -421,45 +553,100 @@
         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);
 
-        if (!r.timeoutExempt) {
-            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
-            mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
+        if (r.finishedCount == 0) {
+            r.dispatchTime = SystemClock.uptimeMillis();
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
         }
 
-        // TODO: apply temp allowlist exemptions
-        // TODO: apply background activity launch exemptions
+        // 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) {
+            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
+            mLocalHandler.sendMessageDelayed(
+                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, 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,
@@ -471,26 +658,74 @@
                         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);
+            }
+        }
+    }
+
     @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.finishedCount + 1; i < r.receivers.size(); i++) {
+                setDeliveryState(null, null, r, i, r.receivers.get(i),
+                        BroadcastRecord.DELIVERY_SKIPPED);
+            }
+        }
+
         return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
     }
 
@@ -498,28 +733,27 @@
             @DeliveryState int deliveryState) {
         checkState(queue.isActive(), "isActive");
 
-        queue.setActiveDeliveryState(deliveryState);
+        final ProcessRecord app = queue.app;
+        final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
 
-        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
-            Slog.w(TAG, "Delivery state of " + queue.getActive() + " to " + queue + " changed to "
-                    + BroadcastRecord.deliveryStateToString(deliveryState));
-        }
+        setDeliveryState(queue, app, r, index, receiver, deliveryState);
 
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
-            if (queue.app != null && !queue.app.isDebugging()) {
+            r.anrCount++;
+            if (app != null && !app.isDebugging()) {
                 mService.appNotResponding(queue.app, TimeoutRecord
-                        .forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
+                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
             }
         } else {
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
         }
 
-        // TODO: if we're the last receiver of this broadcast, record to history
-
         // 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() > MAX_RUNNING_ACTIVE_BROADCASTS);
+                && (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
 
         if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
             // We're on a roll; move onto the next broadcast for this process
@@ -529,21 +763,162 @@
         } 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;
         }
     }
 
+    /**
+     * 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.finishedCount++;
+            notifyFinishReceiver(queue, r, index, receiver);
+
+            if (r.ordered) {
+                if (r.finishedCount < r.receivers.size()) {
+                    // We just finished an ordered receiver, which means the
+                    // next receiver might now be runnable
+                    final Object nextReceiver = r.receivers.get(r.finishedCount);
+                    final BroadcastProcessQueue nextQueue = getProcessQueue(
+                            getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
+                    nextQueue.invalidateRunnableAt();
+                    updateRunnableList(nextQueue);
+                    enqueueUpdateRunningList();
+                } else {
+                    // Everything finished, so deliver final result
+                    scheduleResultTo(r);
+                }
+            }
+        }
+    }
+
+    private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
+        return r.getDeliveryState(index);
+    }
+
     @Override
-    public boolean cleanupDisabledPackageReceiversLocked(String packageName,
-            Set<String> filterByClasses, int userId, boolean doit) {
-        // TODO: implement
-        return false;
+    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 skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
+    }
+
+    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);
+    };
+
+    private boolean skipMatchingBroadcasts(
+            @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
+            @NonNull BroadcastPredicate broadcastPredicate) {
+        // Note that we carefully preserve any "skipped" broadcasts in their
+        // queues so that we follow our normal flow for "finishing" a broadcast,
+        // which is where we handle things like ordered broadcasts.
+        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.removeMatchingBroadcasts(broadcastPredicate,
+                            mBroadcastConsumerSkip)) {
+                        updateRunnableList(leaf);
+                        didSomething = true;
+                    }
+                }
+                leaf = leaf.processNameNext;
+            }
+        }
+        if (didSomething) {
+            enqueueUpdateRunningList();
+        }
+        return didSomething;
     }
 
     @Override
@@ -569,7 +944,7 @@
 
     @Override
     public boolean isIdleLocked() {
-        return (mRunnableHead == null) && mRunning.isEmpty();
+        return (mRunnableHead == null) && (getRunningSize() == 0);
     }
 
     @Override
@@ -594,7 +969,7 @@
 
     @Override
     public String describeStateLocked() {
-        return mRunning.size() + " running";
+        return getRunningSize() + " running";
     }
 
     @Override
@@ -608,17 +983,206 @@
         // TODO: implement
     }
 
+    /**
+     * Heavy verification of internal consistency.
+     */
+    private void checkConsistencyLocked() {
+        // 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);
+            }
+        }
+    }
+
+    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.finishedCount == 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) {
@@ -630,7 +1194,7 @@
             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) {
@@ -641,11 +1205,13 @@
         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)) {
@@ -656,6 +1222,35 @@
         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
     public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
@@ -678,7 +1273,7 @@
         for (int i = 0; i < mProcessQueues.size(); i++) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
-                leaf.dumpLocked(ipw);
+                leaf.dumpLocked(now, ipw);
                 leaf = leaf.processNameNext;
             }
         }
@@ -694,7 +1289,10 @@
             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;
             }
         }
@@ -703,16 +1301,16 @@
         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();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 16eeb7bf..170a25a 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,9 +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;
@@ -42,6 +45,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.PrintWriterPrinter;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -85,22 +89,22 @@
     final 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 @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState 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
     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.
+    @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
     boolean timeoutExempt;  // true if this broadcast is not subject to receiver timeouts
     int resultCode;         // current result code value.
     String resultData;      // current result data value.
@@ -111,6 +115,7 @@
     int anrCount;           // has this broadcast record hit any ANRs?
     int manifestCount;      // number of manifest receivers dispatched.
     int manifestSkipCount;  // number of manifest receivers skipped.
+    int finishedCount;      // number of receivers finished.
     BroadcastQueue queue;   // the outbound queue handling this broadcast
 
     // if set to true, app's process will be temporarily allowed to start activities from background
@@ -125,6 +130,12 @@
     @Nullable
     final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
 
+    String cachedToString;
+    String cachedToShortString;
+
+    /** 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;
@@ -157,16 +168,32 @@
 
     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";
+            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.
@@ -284,8 +311,18 @@
             Object o = receivers.get(i);
             pw.print(prefix);
             pw.print(deliveryStateToString(delivery[i]));
-            pw.print(" "); TimeUtils.formatDuration(duration[i], pw);
-            pw.print(" #"); pw.print(i); pw.print(": ");
+            pw.print(' ');
+            if (scheduledTime[i] != 0) {
+                pw.print("scheduled ");
+                TimeUtils.formatDuration(scheduledTime[i] - enqueueTime, pw);
+                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);
@@ -327,10 +364,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;
@@ -377,7 +414,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;
@@ -537,7 +574,10 @@
 
         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();
@@ -545,6 +585,10 @@
         }
     }
 
+    @DeliveryState int getDeliveryState(int index) {
+        return delivery[index];
+    }
+
     boolean isForeground() {
         return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
     }
@@ -553,6 +597,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;
@@ -606,7 +654,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) */ {
@@ -614,6 +662,14 @@
         }
     }
 
+    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;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
@@ -663,15 +719,57 @@
         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 (cachedToString == null) {
+            String label = intent.getAction();
+            if (label == null) {
+                label = intent.toString();
+            }
+            cachedToString = "BroadcastRecord{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " u" + userId + " " + label + "}";
+        }
+        return cachedToString;
     }
 
     public String toShortString() {
-        return intent.getAction() + "/u" + userId;
+        if (cachedToShortString == null) {
+            String label = intent.getAction();
+            if (label == null) {
+                label = intent.toString();
+            }
+            cachedToShortString = label + "/u" + userId;
+        }
+        return cachedToShortString;
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 992d416..d7b3848 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,6 +27,7 @@
 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;
@@ -1532,6 +1533,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;
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..482e6a7 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;
 
@@ -1212,12 +1211,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) {
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/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 7c7b01c9..82fb1e8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -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);
     }
 
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..30bc8a3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4016,7 +4016,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 +4030,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 +4040,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 +5421,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
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/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/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/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 2cde526..e851f03 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;
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 61225d7..52d630b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1172,13 +1172,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() {
@@ -1598,9 +1595,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();
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index dc7db10..db4cd7f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -171,10 +171,6 @@
     // Our handler.
     private final DisplayControllerHandler mHandler;
 
-    // Asynchronous callbacks into the power manager service.
-    // Only invoked from the handler thread while no locks are held.
-    private final DisplayPowerCallbacks mCallbacks;
-
     // Battery stats.
     @Nullable
     private final IBatteryStats mBatteryStats;
@@ -323,7 +319,10 @@
 
     // The raw non-debounced proximity sensor state.
     private int mPendingProximity = PROXIMITY_UNKNOWN;
-    private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
+
+    // -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.
@@ -339,9 +338,6 @@
     // turning off the screen.
     private boolean mPendingScreenOff;
 
-    // True if we have unfinished business and are holding a suspend blocker.
-    private boolean mUnfinishedBusiness;
-
     // The elapsed real time when the screen on was blocked.
     private long mScreenOnBlockStartRealTime;
     private long mScreenOffBlockStartRealTime;
@@ -407,6 +403,10 @@
     // 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;
 
@@ -467,18 +467,6 @@
 
     private boolean mIsRbcActive;
 
-    // Whether there's a callback to tell listeners the display has changed scheduled to run. 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.
-    private boolean mOnStateChangedPending;
-
-    // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
-    // suspend blocker acquisitions are pending when shutting down this DPC.
-    // Should only be accessed on the Handler thread.
-    private int mOnProximityPositiveMessages;
-    private int mOnProximityNegativeMessages;
-
     // Animators.
     private ObjectAnimator mColorFadeOnAnimator;
     private ObjectAnimator mColorFadeOffAnimator;
@@ -490,13 +478,6 @@
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
 
-    // Identifiers for suspend blocker acuisition requests
-    private final String mSuspendBlockerIdUnfinishedBusiness;
-    private final String mSuspendBlockerIdOnStateChanged;
-    private final String mSuspendBlockerIdProxPositive;
-    private final String mSuspendBlockerIdProxNegative;
-    private final String mSuspendBlockerIdProxDebounce;
-
     /**
      * Creates the display power controller.
      */
@@ -510,12 +491,8 @@
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+        mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
-        mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
-        mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
-        mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
-        mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
-        mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
 
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -531,7 +508,6 @@
         }
 
         mSettingsObserver = new SettingsObserver(mHandler);
-        mCallbacks = callbacks;
         mSensorManager = sensorManager;
         mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
         mBlanker = blanker;
@@ -989,8 +965,8 @@
                     R.array.config_screenBrighteningThresholds);
             int[] screenDarkeningThresholds = resources.getIntArray(
                     R.array.config_screenDarkeningThresholds);
-            int[] screenThresholdLevels = resources.getIntArray(
-                    R.array.config_screenThresholdLevels);
+            float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
+                    .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
             float screenDarkeningMinThreshold =
                     mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
             float screenBrighteningMinThreshold =
@@ -1143,22 +1119,10 @@
         mHandler.removeCallbacksAndMessages(null);
 
         // Release any outstanding wakelocks we're still holding because of pending messages.
-        if (mUnfinishedBusiness) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
-            mUnfinishedBusiness = false;
-        }
-        if (mOnStateChangedPending) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-            mOnStateChangedPending = false;
-        }
-        for (int i = 0; i < mOnProximityPositiveMessages; i++) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
-        }
-        mOnProximityPositiveMessages = 0;
-        for (int i = 0; i < mOnProximityNegativeMessages; i++) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
-        }
-        mOnProximityNegativeMessages = 0;
+        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+        mWakelockController.releaseStateChangedSuspendBlocker();
+        mWakelockController.releaseProxPositiveSuspendBlocker();
+        mWakelockController.releaseProxNegativeSuspendBlocker();
 
         final float brightness = mPowerState != null
                 ? mPowerState.getScreenBrightness()
@@ -1172,13 +1136,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() {
@@ -1598,9 +1559,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();
             }
@@ -1748,12 +1709,8 @@
         }
 
         // Grab a wake lock if we have unfinished business.
-        if (!finished && !mUnfinishedBusiness) {
-            if (DEBUG) {
-                Slog.d(mTag, "Unfinished business...");
-            }
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
-            mUnfinishedBusiness = true;
+        if (!finished) {
+            mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
         }
 
         // Notify the power manager when ready.
@@ -1772,12 +1729,8 @@
         }
 
         // Release the wake lock when we have no unfinished business.
-        if (finished && mUnfinishedBusiness) {
-            if (DEBUG) {
-                Slog.d(mTag, "Finished business...");
-            }
-            mUnfinishedBusiness = false;
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+        if (finished) {
+            mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
         }
 
         // Record if dozing for future comparison.
@@ -2274,7 +2227,12 @@
                 mPendingProximity = PROXIMITY_UNKNOWN;
                 mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
                 mSensorManager.unregisterListener(mProximitySensorListener);
-                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+                // release wake lock(must be last)
+                boolean proxDebounceSuspendBlockerReleased =
+                        mWakelockController.releaseProxDebounceSuspendBlocker();
+                if (proxDebounceSuspendBlockerReleased) {
+                    mPendingProximityDebounceTime = -1;
+                }
             }
         }
     }
@@ -2294,12 +2252,12 @@
             mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
             if (positive) {
                 mPendingProximity = PROXIMITY_POSITIVE;
-                setPendingProximityDebounceTime(
-                        time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
             } else {
                 mPendingProximity = PROXIMITY_NEGATIVE;
-                setPendingProximityDebounceTime(
-                        time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
             }
 
             // Debounce the new sensor reading.
@@ -2321,7 +2279,13 @@
                 // Sensor reading accepted.  Apply the change then release the wake lock.
                 mProximity = mPendingProximity;
                 updatePowerState();
-                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+                // (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.
@@ -2331,25 +2295,10 @@
         }
     }
 
-    private void clearPendingProximityDebounceTime() {
-        if (mPendingProximityDebounceTime >= 0) {
-            mPendingProximityDebounceTime = -1;
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
-        }
-    }
-
-    private void setPendingProximityDebounceTime(long debounceTime) {
-        if (mPendingProximityDebounceTime < 0) {
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
-        }
-        mPendingProximityDebounceTime = debounceTime;
-    }
-
     private void sendOnStateChangedWithWakelock() {
-        if (!mOnStateChangedPending) {
-            mOnStateChangedPending = true;
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-            mHandler.post(mOnStateChangedRunnable);
+        boolean wakeLockAcquired = mWakelockController.acquireStateChangedSuspendBlocker();
+        if (wakeLockAcquired) {
+            mHandler.post(mWakelockController.getOnStateChangedRunnable());
         }
     }
 
@@ -2510,44 +2459,17 @@
         }
     }
 
-    private final Runnable mOnStateChangedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnStateChangedPending = false;
-            mCallbacks.onStateChanged();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-        }
-    };
-
     private void sendOnProximityPositiveWithWakelock() {
-        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
-        mHandler.post(mOnProximityPositiveRunnable);
-        mOnProximityPositiveMessages++;
+        mWakelockController.acquireProxPositiveSuspendBlocker();
+        mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
     }
 
-    private final Runnable mOnProximityPositiveRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnProximityPositiveMessages--;
-            mCallbacks.onProximityPositive();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
-        }
-    };
 
     private void sendOnProximityNegativeWithWakelock() {
-        mOnProximityNegativeMessages++;
-        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
-        mHandler.post(mOnProximityNegativeRunnable);
+        mWakelockController.acquireProxNegativeSuspendBlocker();
+        mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
     }
 
-    private final Runnable mOnProximityNegativeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnProximityNegativeMessages--;
-            mCallbacks.onProximityNegative();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
-        }
-    };
 
     @Override
     public void dump(final PrintWriter pw) {
@@ -2606,7 +2528,6 @@
         pw.println();
         pw.println("Display Power Controller Thread State:");
         pw.println("  mPowerRequest=" + mPowerRequest);
-        pw.println("  mUnfinishedBusiness=" + mUnfinishedBusiness);
         pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
         pw.println("  mProximitySensor=" + mProximitySensor);
         pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
@@ -2644,9 +2565,6 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
-        pw.println("  mOnStateChangePending=" + mOnStateChangedPending);
-        pw.println("  mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
-        pw.println("  mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
 
         if (mScreenBrightnessRampAnimator != null) {
             pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
@@ -2684,6 +2602,12 @@
             mDisplayWhiteBalanceController.dump(pw);
             mDisplayWhiteBalanceSettings.dump(pw);
         }
+
+        pw.println();
+
+        if (mWakelockController != null) {
+            mWakelockController.dumpLocal(pw);
+        }
     }
 
     private static String proximityToString(int state) {
@@ -2989,28 +2913,6 @@
         }
     }
 
-    @VisibleForTesting
-    String getSuspendBlockerUnfinishedBusinessId(int displayId) {
-        return "[" + displayId + "]unfinished business";
-    }
-
-    String getSuspendBlockerOnStateChangedId(int displayId) {
-        return "[" + displayId + "]on state changed";
-    }
-
-    String getSuspendBlockerProxPositiveId(int displayId) {
-        return "[" + displayId + "]prox positive";
-    }
-
-    String getSuspendBlockerProxNegativeId(int displayId) {
-        return "[" + displayId + "]prox negative";
-    }
-
-    @VisibleForTesting
-    String getSuspendBlockerProxDebounceId(int displayId) {
-        return "[" + displayId + "]prox debounce";
-    }
-
     /** Functional interface for providing time. */
     @VisibleForTesting
     interface Clock {
@@ -3036,6 +2938,11 @@
                 FloatProperty<DisplayPowerState> secondProperty) {
             return new DualRampAnimator(dps, firstProperty, secondProperty);
         }
+
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return new WakelockController(displayId, displayPowerCallbacks);
+        }
     }
 
     static class CachedBrightnessInfo {
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/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e03a46c..3ee3503 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -266,10 +266,6 @@
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
-    // Last return value of getPhysicalAddress(). Only updated on calls of getPhysicalAddress().
-    // Does not represent the current physical address at all times. Not to be used as a cache.
-    private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
-
     /**
      * Interface to report send result.
      */
@@ -2082,15 +2078,9 @@
         @Override
         public int getPhysicalAddress() {
             initBinderCall();
-            runOnServiceThread(new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (mLock) {
-                        mPhysicalAddress = mHdmiCecNetwork.getPhysicalAddress();
-                    }
-                }
-            });
-            return mPhysicalAddress;
+            synchronized (mLock) {
+                return mHdmiCecNetwork.getPhysicalAddress();
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 32f17dc1..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,8 +48,13 @@
 /**
  * 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:
@@ -69,10 +75,9 @@
     @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;
@@ -92,9 +97,9 @@
         mUEventManager = uEventManager;
     }
 
-    void systemRunning() {
+    public void systemRunning() {
         Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                .registerInputDeviceListener(this, mHandler);
+                .registerInputDeviceListener(mInputDeviceListener, mHandler);
     }
 
     /**
@@ -102,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);
@@ -131,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) {
@@ -144,36 +149,35 @@
             }
 
             updatePollingLocked(true /*delayStart*/);
-            notifyBatteryListener(listenerRecord, deviceState);
+            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 (mMonitoredDeviceStates.isEmpty() || !mIsInteractive) {
+        if (mDeviceMonitors.isEmpty() || !mIsInteractive) {
             // Stop polling.
             mIsPolling = false;
             mHandler.removeCallbacks(this::handlePollEvent);
@@ -196,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);
     }
 
@@ -207,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) {
@@ -247,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()) {
@@ -288,15 +292,14 @@
         }
     }
 
-    // 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());
             }
         }
     }
@@ -307,11 +310,11 @@
                 return;
             }
             final long eventTime = SystemClock.uptimeMillis();
-            mMonitoredDeviceStates.forEach((deviceId, deviceState) -> {
+            mDeviceMonitors.forEach((deviceId, monitor) -> {
                 // Re-acquire lock in the lambda to silence error-prone build warnings.
                 synchronized (mLock) {
-                    if (deviceState.updateBatteryState(eventTime)) {
-                        notifyAllListenersForDeviceLocked(deviceState);
+                    if (monitor.updateBatteryState(eventTime)) {
+                        notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
                     }
                 }
             });
@@ -319,62 +322,90 @@
         }
     }
 
-    void onInteractiveChanged(boolean interactive) {
+    /** 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*/);
         }
     }
 
-    void dump(PrintWriter pw, String prefix) {
+    public void dump(PrintWriter pw, String prefix) {
         synchronized (mLock) {
-            pw.println(prefix + TAG + ": "
-                    + mListenerRecords.size() + " battery listeners"
-                    + ", Polling = " + mIsPolling
+            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;
@@ -390,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();
@@ -412,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.
@@ -483,7 +521,7 @@
                 }
             };
 
-            abstract void onUEvent(long eventTime);
+            public abstract void onUEvent(long eventTime);
         }
 
         default void addListener(UEventListener listener, String match) {
@@ -494,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 98%
rename from core/java/android/hardware/input/InputManagerInternal.java
rename to services/core/java/com/android/server/input/InputManagerInternal.java
index fc6bc55..36099b0 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;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 05c4f77..beb524e 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;
@@ -2306,14 +2306,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
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/InputMethodDeviceConfigs.java b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
index 9d5393f..2160b65 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
@@ -25,7 +25,7 @@
  * 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;
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed17b9ca..08ccccd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -75,6 +75,7 @@
 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;
@@ -92,7 +93,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;
@@ -188,6 +188,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;
@@ -2167,13 +2168,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 +2183,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 return getEnabledInputMethodSubtypeListLocked(imiId,
-                        allowsImplicitlySelectedSubtypes, userId);
+                        allowsImplicitlyEnabledSubtypes, userId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2191,7 +2192,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 +2205,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 +2215,7 @@
         final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
                 true);
         return settings.getEnabledInputMethodSubtypeListLocked(
-                imi, allowsImplicitlySelectedSubtypes);
+                imi, allowsImplicitlyEnabledSubtypes);
     }
 
     /**
@@ -4214,6 +4215,46 @@
         }
     }
 
+    @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(mAppOpsManager,
+                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);
+        }
+    }
+
     /**
      * This is kept due to {@code @UnsupportedAppUsage} in
      * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 1747b5c..beeaa90 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -35,6 +35,7 @@
 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,6 +43,7 @@
 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;
@@ -415,10 +417,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 +671,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();
+                            if (implicitlyEnabledSubtypes != null) {
+                                final int N = implicitlyEnabledSubtypes.size();
                                 for (int i = 0; i < N; ++i) {
-                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
+                                    final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
                                         return subtypeHashCode;
                                     }
@@ -901,6 +903,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/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index e7f6e67..349b94b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -425,18 +425,15 @@
     }
 
     private void onPackageReset(String packageName) {
-        // invoked when a package is "force quit" - move off the main thread
-        FgThread.getExecutor().execute(
-                () ->
-                        updateRegistrations(
-                                registration -> {
-                                    if (registration.getIdentity().getPackageName().equals(
-                                            packageName)) {
-                                        registration.remove();
-                                    }
+        updateRegistrations(
+                registration -> {
+                    if (registration.getIdentity().getPackageName().equals(
+                            packageName)) {
+                        registration.remove();
+                    }
 
-                                    return false;
-                                }));
+                    return false;
+                });
     }
 
     private boolean isResetableForPackage(String packageName) {
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/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/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
index 91b0212..b0fe55d 100644
--- a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
@@ -27,6 +27,7 @@
 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 {
@@ -120,7 +121,9 @@
                                     context.getPackageManager().getApplicationInfo(packageName,
                                             PackageManager.ApplicationInfoFlags.of(0));
                             if (!appInfo.enabled) {
-                                notifyPackageReset(packageName);
+                                // move off main thread
+                                FgThread.getExecutor().execute(
+                                        () -> notifyPackageReset(packageName));
                             }
                         } catch (PackageManager.NameNotFoundException e) {
                             return;
@@ -130,7 +133,8 @@
                 case Intent.ACTION_PACKAGE_REMOVED:
                     // fall through
                 case Intent.ACTION_PACKAGE_RESTARTED:
-                    notifyPackageReset(packageName);
+                    // 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 bd75251..338a995 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2409,18 +2409,15 @@
     }
 
     private void onPackageReset(String packageName) {
-        // invoked when a package is "force quit" - move off the main thread
-        FgThread.getExecutor().execute(
-                () ->
-                        updateRegistrations(
-                                registration -> {
-                                    if (registration.getIdentity().getPackageName().equals(
-                                            packageName)) {
-                                        registration.remove();
-                                    }
+        updateRegistrations(
+                registration -> {
+                    if (registration.getIdentity().getPackageName().equals(
+                            packageName)) {
+                        registration.remove();
+                    }
 
-                                    return false;
-                                }));
+                    return false;
+                });
     }
 
     private boolean isResetableForPackage(String packageName) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8ab3a94..533d1b0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2940,6 +2940,7 @@
 
     @Override
     public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
+        checkPasswordReadPermission();
         try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
             if (cred == null) {
                 return false;
@@ -2951,6 +2952,7 @@
 
     @Override
     public void removeCachedUnifiedChallenge(int userId) {
+        checkWritePermission();
         mManagedProfilePasswordCache.removePassword(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/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/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 82b5c11..1fdf394 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -621,10 +621,9 @@
         }
     }
 
-    //TODO(b/136703681): Review this is handling multi-user properly.
-    void switchUser() {
+    // TODO(b/136703681): Review this is handling multi-user properly.
+    void switchUser(int userId) {
         synchronized (mLock) {
-            int userId = ActivityManager.getCurrentUser();
             if (mCurrentUserId != userId) {
                 final int oldUserId = mCurrentUserId;
                 mCurrentUserId = userId; // do this first
@@ -1243,6 +1242,8 @@
                 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>");
             }
@@ -1261,7 +1262,6 @@
         public final int mRouterId;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
-        public MediaRoute2Info mSelectedRoute;
 
         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
                 String packageName, boolean hasConfigureWifiDisplayPermission,
@@ -1301,6 +1301,8 @@
             pw.println(indent + "mHasModifyAudioRoutingPermission="
                     + mHasModifyAudioRoutingPermission);
             pw.println(indent + "mRouterId=" + mRouterId);
+
+            mDiscoveryPreference.dump(pw, indent);
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4f0da795..d7d0b42 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;
@@ -146,18 +148,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) {
+                                switchUser(newUserId);
+                            }
+                        },
+                        TAG);
+        switchUser(ActivityManager.getCurrentUser());
     }
 
     @Override
@@ -634,9 +645,8 @@
         }
     }
 
-    void switchUser() {
+    void switchUser(int userId) {
         synchronized (mLock) {
-            int userId = ActivityManager.getCurrentUser();
             if (mCurrentUserId != userId) {
                 final int oldUserId = mCurrentUserId;
                 mCurrentUserId = userId; // do this first
@@ -653,7 +663,7 @@
                 }
             }
         }
-        mService2.switchUser();
+        mService2.switchUser(userId);
     }
 
     void clientDied(ClientRecord clientRecord) {
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 8e944b7..bea6168 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -267,8 +267,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/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..da6f042 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -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..7ff91f82 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;
@@ -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..cd074c0 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -385,6 +385,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 +432,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();
         }
@@ -498,6 +507,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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 1746d93..81d47a0 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);
@@ -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);
@@ -2059,9 +2060,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 +2685,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 +2814,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
@@ -4297,7 +4298,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/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 3fb4066..178d0ea 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
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 9481f8a..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;
@@ -210,7 +211,6 @@
 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.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -223,7 +223,6 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.resolution.ComponentResolver;
 import com.android.server.pm.resolution.ComponentResolverApi;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -1073,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();
@@ -1533,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);
     }
 
@@ -5491,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;
                     }
                 }
@@ -5546,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;
                 }
             }
@@ -6019,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() {
@@ -7298,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 03f17bd..9050722 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -42,15 +42,17 @@
 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.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,13 +1205,13 @@
 
     @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(value);
+        pkgState.addUsesLibraryInfo(new SharedLibraryWrapper(value));
         return this;
     }
 
@@ -1326,6 +1328,13 @@
         return this;
     }
 
+    @NonNull
+    @Override
+    public PackageUserState getStateForUser(@NonNull UserHandle user) {
+        PackageUserState userState = getUserStates().get(user.getIdentifier());
+        return userState == null ? PackageUserState.DEFAULT : userState;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1487,10 +1496,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/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..bce6834 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -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);
         }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 80e9646..0558fbd 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -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());
             }
 
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/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index c3eb2fd..51bb412 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -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..c39cbae 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5199,8 +5199,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 +5214,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 +5295,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 +5412,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;
                     }
                 }
@@ -6618,7 +6658,7 @@
 
         @Override
         public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
-            return removeUserUnchecked(userId);
+            return removeUserWithProfilesUnchecked(userId);
         }
 
         @Override
@@ -6822,13 +6862,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 +6898,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/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..bc3d7a6 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;
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..ca8ba6c 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
@@ -91,7 +91,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 +102,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 +230,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();
     }
 
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 fe63dec..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)
@@ -399,6 +402,8 @@
     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) {
@@ -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))
@@ -1209,8 +1259,8 @@
 
     @Nullable
     @Override
-    public String getSdkLibName() {
-        return sdkLibName;
+    public String getSdkLibraryName() {
+        return sdkLibraryName;
     }
 
     @Override
@@ -1279,8 +1329,8 @@
 
     @Nullable
     @Override
-    public String getStaticSharedLibName() {
-        return staticSharedLibName;
+    public String getStaticSharedLibraryName() {
+        return staticSharedLibraryName;
     }
 
     @Override
@@ -2218,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;
     }
 
@@ -2261,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;
     }
 
@@ -2977,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);
@@ -3127,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);
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 e07b77e..5108fcd 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -63,6 +63,69 @@
 @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
 
     /**
@@ -317,15 +380,6 @@
     int getLargestWidthLimitDp();
 
     /**
-     * Library names this package is declared as, for use by other packages with "uses-library".
-     *
-     * @see R.styleable#AndroidManifestLibrary
-     * @hide
-     */
-    @NonNull
-    List<String> getLibraryNames();
-
-    /**
      * The resource ID used to provide the application's locales configuration.
      *
      * @see R.styleable#AndroidManifestApplication_localeConfig
@@ -730,13 +784,6 @@
     int getRoundIconRes();
 
     /**
-     * @see R.styleable#AndroidManifestSdkLibrary_name
-     * @hide
-     */
-    @Nullable
-    String getSdkLibName();
-
-    /**
      * @see R.styleable#AndroidManifestSdkLibrary_versionMajor
      * @hide
      */
@@ -844,13 +891,6 @@
     int[] getSplitRevisionCodes();
 
     /**
-     * @see R.styleable#AndroidManifestStaticLibrary_name
-     * @hide
-     */
-    @Nullable
-    String getStaticSharedLibName();
-
-    /**
      * @see R.styleable#AndroidManifestStaticLibrary_version
      * @hide
      */
@@ -864,13 +904,6 @@
     int getTargetSandboxVersion();
 
     /**
-     * @see ApplicationInfo#targetSdkVersion
-     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
-     * @hide
-     */
-    int getTargetSdkVersion();
-
-    /**
      * @see ApplicationInfo#taskAffinity
      * @see R.styleable#AndroidManifestApplication_taskAffinity
      * @hide
@@ -1118,12 +1151,6 @@
     boolean isCrossProfile();
 
     /**
-     * @see ApplicationInfo#FLAG_DEBUGGABLE
-     * @hide
-     */
-    boolean isDebuggable();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
      * @hide
      */
@@ -1198,12 +1225,6 @@
     boolean isHasFragileUserData();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
-     * @hide
-     */
-    boolean isIsolatedSplitLoading();
-
-    /**
      * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
      * @hide
      */
@@ -1354,12 +1375,6 @@
     boolean isSdkLibrary();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
-     * @hide
-     */
-    boolean isSignedWithPlatformKey();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_STATIC_SHARED_LIBRARY
      * @hide
      */
@@ -1445,24 +1460,12 @@
     boolean isUse32BitAbi();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
-     * @hide
-     */
-    boolean isUseEmbeddedDex();
-
-    /**
      * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
      * @hide
      */
     boolean isUsesCleartextTraffic();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API
-     * @hide
-     */
-    boolean isUsesNonSdkApi();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_VENDOR
      * @hide
      */
@@ -1477,10 +1480,4 @@
      * @hide
      */
     boolean isVisibleToInstantApps();
-
-    /**
-     * @see ApplicationInfo#FLAG_VM_SAFE_MODE
-     * @hide
-     */
-    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 c0e063d..a6e6016 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,8 +22,8 @@
 import android.annotation.UserIdInt;
 import android.content.pm.ApplicationInfo;
 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;
 
@@ -42,8 +42,6 @@
 @Immutable
 public interface PackageState {
 
-    // Methods below this comment are not yet exposed as API
-
     /*
      * Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be
      * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data.
@@ -83,11 +81,9 @@
      * 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 parseable on
-     * an updated SDK version, causing it to be rejectd, but the state associated with it retained,
+     * 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.
-     *
-     * @hide
      */
     @Nullable
     AndroidPackage getAndroidPackage();
@@ -95,12 +91,58 @@
     /**
      * The non-user-specific UID, or the UID if the user ID is
      * {@link android.os.UserHandle#SYSTEM}.
-     *
-     * @hide
      */
     int getAppId();
 
     /**
+     * @see AndroidPackage#getPackageName()
+     */
+    @NonNull
+    String getPackageName();
+
+    /**
+     * @see ApplicationInfo#primaryCpuAbi
+     */
+    @Nullable
+    String getPrimaryCpuAbi();
+
+    /**
+     * @see ApplicationInfo#secondaryCpuAbi
+     */
+    @Nullable
+    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.
      *
@@ -165,13 +207,6 @@
     Map<String, Set<String>> getMimeGroups();
 
     /**
-     * @see AndroidPackage#getPackageName()
-     * @hide
-     */
-    @NonNull
-    String getPackageName();
-
-    /**
      * @see AndroidPackage#getPath()
      * @hide
      */
@@ -179,20 +214,6 @@
     File getPath();
 
     /**
-     * @see ApplicationInfo#primaryCpuAbi
-     * @hide
-     */
-    @Nullable
-    String getPrimaryCpuAbi();
-
-    /**
-     * @see ApplicationInfo#secondaryCpuAbi
-     * @hide
-     */
-    @Nullable
-    String getSecondaryCpuAbi();
-
-    /**
      * Whether the package shares the same user ID as other packages
      * @hide
      */
@@ -239,14 +260,6 @@
     List<String> getUsesLibraryFiles();
 
     /**
-     * @see R.styleable#AndroidManifestUsesLibrary
-     * @hide
-     */
-    @Immutable.Ignore
-    @NonNull
-    List<SharedLibraryInfo> getUsesLibraryInfos();
-
-    /**
      * @see R.styleable#AndroidManifestUsesSdkLibrary
      * @hide
      */
@@ -327,12 +340,6 @@
     boolean isOem();
 
     /**
-     * @see AndroidPackage#isPrivileged()
-     * @hide
-     */
-    boolean isPrivileged();
-
-    /**
      * @see AndroidPackage#isProduct()
      * @hide
      */
@@ -345,12 +352,6 @@
     boolean isRequiredForSystemUser();
 
     /**
-     * @see AndroidPackage#isSystem()
-     * @hide
-     */
-    boolean isSystem();
-
-    /**
      * @see AndroidPackage#isSystemExt()
      * @hide
      */
@@ -363,14 +364,6 @@
     boolean isUpdateAvailable();
 
     /**
-     * Whether this app is on the /data partition having been upgraded from a preinstalled app on a
-     * system partition.
-     *
-     * @hide
-     */
-    boolean isUpdatedSystemApp();
-
-    /**
      * Whether this app is packaged in an updated apex.
      *
      * @hide
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 28309c7..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;
 
@@ -140,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
@@ -181,7 +181,7 @@
         mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
         mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
         mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
-        mUsesLibraryInfos = Collections.unmodifiableList(pkgState.getUsesLibraryInfos());
+        mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
         mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
         setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
         setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
@@ -201,6 +201,13 @@
         }
     }
 
+    @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);
@@ -469,8 +476,7 @@
         }
 
         @DataClass.Generated.Member
-        public @NonNull
-        ArraySet<String> getDisabledComponents() {
+        public @NonNull ArraySet<String> getDisabledComponents() {
             return mDisabledComponents;
         }
 
@@ -536,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() {}
 
@@ -660,8 +666,8 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
-        return mUsesLibraryInfos;
+    public @NonNull List<SharedLibrary> getUsesLibraries() {
+        return mUsesLibraries;
     }
 
     @DataClass.Generated.Member
@@ -691,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/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index 1ae00d3..b22c038 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -29,7 +29,6 @@
 
 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.
@@ -42,13 +41,13 @@
  * @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();
@@ -72,7 +71,7 @@
     }
 
     @NonNull
-    public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
+    public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryWrapper value) {
         usesLibraryInfos = CollectionUtils.add(usesLibraryInfos, value);
         return this;
     }
@@ -143,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) {
@@ -154,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;
     }
@@ -216,7 +227,7 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<SharedLibraryInfo> getUsesLibraryInfos() {
+    public @NonNull List<SharedLibraryWrapper> getUsesLibraryInfos() {
         return usesLibraryInfos;
     }
 
@@ -265,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/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index a1b6f1d..a68e59b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -33,12 +33,18 @@
  *
  * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 @Immutable
 public interface PackageUserState {
 
     /**
+     * @return whether the package is marked as installed
+     */
+    boolean isInstalled();
+
+    // Methods below this comment are not yet exposed as API
+
+    /**
      * @hide
      */
     @NonNull
@@ -150,12 +156,6 @@
     boolean isHidden();
 
     /**
-     * @return whether the package is marked as installed for all users
-     * @hide
-     */
-    boolean isInstalled();
-
-    /**
      * @return whether the package is marked as an ephemeral app, which restricts permissions,
      * features, visibility
      * @hide
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/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 a8d48ae..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
@@ -2164,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);
@@ -2355,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 {
@@ -2393,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 02d7074..07f5bcf 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;
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/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/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 0ec8826c..d973ca6 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,13 +18,16 @@
 
 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.util.Objects;
 
 /**
@@ -59,27 +62,22 @@
     }
 
     @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) {
+        AlarmManagerInternal alarmManagerInternal =
+                LocalServices.getService(AlarmManagerInternal.class);
+        alarmManagerInternal.setTimeZone(zoneId, confidence);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 66c23f5..9b35779 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -22,6 +22,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 android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,6 +41,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
 
 import java.time.Duration;
 import java.util.List;
@@ -73,19 +76,20 @@
         @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
 
         /**
-         * Returns true if the device has had an explicit time zone set.
-         */
-        boolean isDeviceTimeZoneInitialized();
-
-        /**
          * Returns the device's currently configured time zone.
          */
-        String getDeviceTimeZone();
+        @NonNull String getDeviceTimeZone();
 
         /**
-         * Sets the device's time zone.
+         * Returns the confidence of the device's current time zone.
          */
-        void setDeviceTimeZone(@NonNull String zoneId);
+        @TimeZoneConfidence int getDeviceTimeZoneConfidence();
+
+        /**
+         * Sets the device's time zone and associated confidence.
+         */
+        void setDeviceTimeZoneAndConfidence(
+                @NonNull String zoneId, @TimeZoneConfidence int confidence);
 
         /**
          * Returns the time according to the elapsed realtime clock, the same as {@link
@@ -620,25 +624,32 @@
     @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);
+                        + ", cause=" + cause
+                        + ", currentScore=" + currentConfidence
+                        + ", newConfidence=" + newConfidence);
             }
             return;
         }
 
-        mEnvironment.setDeviceTimeZone(newZoneId);
-        String logMsg = "Set device time zone."
+        mEnvironment.setDeviceTimeZoneAndConfidence(newZoneId, newConfidence);
+        String logMsg = "Set device time zone or higher confidence."
                 + ", currentZoneId=" + currentZoneId
                 + ", newZoneId=" + newZoneId
-                + ", cause=" + cause;
+                + ", cause=" + cause
+                + ", newConfidence=" + newConfidence;
         logTimeZoneDetectorChange(logMsg);
     }
 
@@ -710,9 +721,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
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 f1b011f..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;
@@ -965,6 +966,13 @@
                         availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
                     }
 
+                    // Count letterbox into nonMagnifiedBounds
+                    if (windowState.areAppWindowBoundsLetterboxed()) {
+                        Region letterboxBounds = getLetterboxBounds(windowState);
+                        nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
+                        availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
+                    }
+
                     // Update accounted bounds
                     Region accountedBounds = mTempRegion2;
                     accountedBounds.set(mMagnificationRegion);
@@ -1014,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
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 08d2e69..f0de1d3 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1253,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 d8d75ed..9ed77fc 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1354,10 +1354,6 @@
         return true;
     }
 
-    void setAppTimeTracker(AppTimeTracker att) {
-        appTimeTracker = att;
-    }
-
     /** Update the saved state of an activity. */
     void setSavedState(@Nullable Bundle savedState) {
         mIcicle = savedState;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5bddae6..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;
@@ -1578,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
@@ -2268,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
@@ -2296,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;
 
@@ -2309,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/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 8c5f053..7d9ae87 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,8 +202,7 @@
         // 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 (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
-                    && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) {
+            if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
                 // Expect a screenshot layer will cover the non seamless windows.
                 continue;
             }
@@ -489,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()) {
@@ -554,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/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 c6fe017..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
@@ -4547,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();
@@ -4900,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");
@@ -6187,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;
@@ -6803,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/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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ba834de..b2ec3f3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3102,9 +3102,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..a753b55 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;
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 1508870..63c6c35 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -196,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;
@@ -1188,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()
@@ -2371,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];
     }
 
@@ -2783,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;
             }
@@ -4349,14 +4338,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
@@ -5209,26 +5190,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();
         }
     }
@@ -5447,10 +5421,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 {
@@ -5621,11 +5592,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);
@@ -5636,12 +5607,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) {
@@ -5727,22 +5692,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 2cfc563..4f1a561 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1719,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);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 69d86b6..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);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e23e206..1497a19 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -309,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;
@@ -475,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));
             }
         }
     };
@@ -900,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));
             }
         }
 
@@ -1014,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;
@@ -1177,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(
@@ -1798,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()) {
@@ -1834,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();
             }
 
@@ -2304,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)) {
@@ -3135,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
@@ -3804,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();
@@ -3822,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);
@@ -3836,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();
         }
     }
 
@@ -6645,7 +6676,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);
@@ -8387,10 +8417,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));
         }
     }
 
@@ -9282,4 +9309,46 @@
                         "Unexpected letterbox background type: " + letterboxBackgroundType);
         }
     }
+
+    @Override
+    public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
+            ScreenCapture.ScreenCaptureListener listener) {
+        Slog.d(TAG, "captureDisplay");
+        if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener);
+    }
+
+    @VisibleForTesting
+    ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId,
+            @Nullable ScreenCapture.CaptureArgs captureArgs) {
+        final SurfaceControl displaySurfaceControl;
+        synchronized (mGlobalLock) {
+            DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException("Trying to screenshot and invalid display: "
+                        + displayId);
+            }
+
+            displaySurfaceControl = displayContent.getSurfaceControl();
+
+            if (captureArgs == null) {
+                captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                        .build();
+            }
+
+            if (captureArgs.mSourceCrop.isEmpty()) {
+                displayContent.getBounds(mTmpRect);
+                mTmpRect.offsetTo(0, 0);
+            } else {
+                mTmpRect.set(captureArgs.mSourceCrop);
+            }
+        }
+
+        return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
+                        .setSourceCrop(mTmpRect)
+                        .build();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e1477d..e2f833c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -91,8 +91,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;
 
@@ -482,7 +480,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()) {
@@ -592,16 +590,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);
+                    });
                 }
             }
 
@@ -716,7 +708,7 @@
 
         final int childWindowingMode = c.getActivityWindowingMode();
         if (childWindowingMode > -1) {
-            tr.setActivityWindowingMode(childWindowingMode);
+            tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
         }
 
         if (t != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4004d65..68fabc5 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()
@@ -4568,7 +4580,7 @@
     float translateToWindowX(float x) {
         float winX = x - mWindowFrames.mFrame.left;
         if (mGlobalScale != 1f) {
-            winX *= mGlobalScale;
+            winX *= mInvGlobalScale;
         }
         return winX;
     }
@@ -4576,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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 42dfee3..a86222e 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);
         }
@@ -12561,7 +12567,8 @@
             return false;
         }
         mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getAlarmManager().setTimeZone(timeZone));
+                mInjector.getAlarmManagerInternal()
+                        .setTimeZone(timeZone, TIME_ZONE_CONFIDENCE_HIGH));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_TIME_ZONE)
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 7652d6d..d8f282a 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 =
@@ -399,7 +399,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";
@@ -760,15 +761,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
@@ -2735,6 +2729,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/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 b41fd39..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,12 +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 android.content.pm.*
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -32,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
@@ -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,
@@ -550,6 +535,7 @@
             SparseArray<IntArray>().apply {
                 put(0, intArrayOf(-1))
                 put(1, intArrayOf(0))
+                put(2, intArrayOf(1))
             }
         )
         .setSplitHasCode(0, true)
@@ -599,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/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index 7e9e433..8a855e5 100644
--- 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
@@ -95,8 +95,6 @@
             ParsedProvider::getUriPermissionPatterns,
             ParsedService::getIntents,
             ParsedService::getProperties,
-            SharedLibraryInfo::getAllCodePaths,
-            SharedLibraryInfo::getDependencies,
             Intent::getCategories,
             PackageUserState::getDisabledComponents,
             PackageUserState::getEnabledComponents,
@@ -149,6 +147,20 @@
      */
     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",
@@ -158,7 +170,7 @@
             0,
             VersionedPackage("versionedPackage0", 1),
             listOf(VersionedPackage("versionedPackage1", 2)),
-            emptyList(),
+            sharedLibraryDependency,
             false
         ))
         pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType"))
@@ -233,7 +245,13 @@
                     }
 
                     val value = try {
-                        collection.stream().findFirst().get()!!
+                        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")
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..446317e 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;
@@ -323,7 +325,12 @@
         }
 
         @Override
-        void setKernelTimezone(int minutesWest) {
+        void setKernelTimeZoneOffset(int utcOffsetMillis) {
+            // Do nothing.
+        }
+
+        @Override
+        void syncKernelTimeZoneOffset() {
             // Do nothing.
         }
 
@@ -3421,12 +3428,13 @@
     public void setTimeZoneImpl() {
         final long durationMs = 20000L;
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
-        mService.setTimeZoneImpl("UTC");
+        mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH);
         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
index af96346..e09b80e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -18,17 +18,33 @@
 
 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.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;
@@ -41,6 +57,8 @@
 @SmallTest
 @RunWith(MockitoJUnitRunner.class)
 public class BroadcastQueueModernImplTest {
+    private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
+
     @Mock ActivityManagerService mAms;
 
     @Mock BroadcastProcessQueue mQueue1;
@@ -49,6 +67,8 @@
     @Mock BroadcastProcessQueue mQueue4;
 
     HandlerThread mHandlerThread;
+
+    BroadcastConstants mConstants;
     BroadcastQueueModernImpl mImpl;
 
     BroadcastProcessQueue mHead;
@@ -59,9 +79,10 @@
 
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
+
+        mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
         mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
-                new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS),
-                new BroadcastConstants(Settings.Global.BROADCAST_BG_CONSTANTS));
+                mConstants, mConstants);
 
         doReturn(1L).when(mQueue1).getRunnableAt();
         doReturn(2L).when(mQueue2).getRunnableAt();
@@ -69,6 +90,11 @@
         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);
@@ -94,8 +120,31 @@
         }
     }
 
+    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, null, 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 testRunnableAt_Simple() {
+    public void testRunnableList_Simple() {
         assertRunnableList(List.of(), mHead);
 
         mHead = insertIntoRunnableList(mHead, mQueue1);
@@ -106,7 +155,7 @@
     }
 
     @Test
-    public void testRunnableAt_InsertLast() {
+    public void testRunnableList_InsertLast() {
         mHead = insertIntoRunnableList(mHead, mQueue1);
         mHead = insertIntoRunnableList(mHead, mQueue2);
         mHead = insertIntoRunnableList(mHead, mQueue3);
@@ -115,7 +164,7 @@
     }
 
     @Test
-    public void testRunnableAt_InsertFirst() {
+    public void testRunnableList_InsertFirst() {
         mHead = insertIntoRunnableList(mHead, mQueue4);
         mHead = insertIntoRunnableList(mHead, mQueue3);
         mHead = insertIntoRunnableList(mHead, mQueue2);
@@ -124,7 +173,7 @@
     }
 
     @Test
-    public void testRunnableAt_InsertMiddle() {
+    public void testRunnableList_InsertMiddle() {
         mHead = insertIntoRunnableList(mHead, mQueue1);
         mHead = insertIntoRunnableList(mHead, mQueue3);
         mHead = insertIntoRunnableList(mHead, mQueue2);
@@ -132,7 +181,7 @@
     }
 
     @Test
-    public void testRunnableAt_Remove() {
+    public void testRunnableList_Remove() {
         mHead = insertIntoRunnableList(mHead, mQueue1);
         mHead = insertIntoRunnableList(mHead, mQueue2);
         mHead = insertIntoRunnableList(mHead, mQueue3);
@@ -156,4 +205,128 @@
         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() {
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        assertFalse(queue.isRunnable());
+        assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+    }
+
+    /**
+     * 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() {
+        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.enqueueBroadcast(airplaneRecord, 0);
+
+        queue.setProcessCached(false);
+        final long notCachedRunnableAt = queue.getRunnableAt();
+        queue.setProcessCached(true);
+        final long cachedRunnableAt = queue.getRunnableAt();
+        assertTrue(cachedRunnableAt > notCachedRunnableAt);
+    }
+
+    /**
+     * Queue with foreground broadcast is always runnable immediately,
+     * regardless of process cached state.
+     */
+    @Test
+    public void testRunnableAt_Foreground() {
+        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.enqueueBroadcast(airplaneRecord, 0);
+
+        queue.setProcessCached(false);
+        assertTrue(queue.isRunnable());
+        assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+
+        queue.setProcessCached(true);
+        assertTrue(queue.isRunnable());
+        assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+    }
+
+    /**
+     * 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));
+
+        // 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 d3ceec8..86d6169 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,38 @@
 
 package com.android.server.am;
 
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+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,12 +55,16 @@
 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.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -58,6 +78,7 @@
 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,21 +86,29 @@
 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.List;
+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.
  */
 @MediumTest
 @RunWith(Parameterized.class)
-@SuppressWarnings("GuardedBy")
 public class BroadcastQueueTest {
     private static final String TAG = "BroadcastQueueTest";
 
@@ -104,15 +133,28 @@
     private ProcessList mProcessList;
     @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<>();
+
     @Parameters(name = "impl={0}")
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
@@ -136,21 +178,30 @@
         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, false);
+            final ProcessRecord res = makeActiveProcessRecord(ai, processName,
+                    ProcessBehavior.NORMAL, UnaryOperator.identity());
             mHandlerThread.getThreadHandler().post(() -> {
                 synchronized (mAms) {
                     mQueue.onApplicationAttachedLocked(res);
@@ -159,11 +210,22 @@
             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) {
                 return false;
@@ -189,6 +251,18 @@
         }
     }
 
+    @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);
@@ -210,26 +284,72 @@
         }
     }
 
-    private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
-        final ApplicationInfo ai = makeApplicationInfo(packageName);
-        return makeActiveProcessRecord(ai, ai.processName, false);
+    /**
+     * 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 makeActiveProcessRecordWedged(String packageName) throws Exception {
-        final ApplicationInfo ai = makeApplicationInfo(packageName);
-        return makeActiveProcessRecord(ai, ai.processName, true);
+    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,
+    }
+
+    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,
-            boolean wedged) throws Exception {
-        final ProcessRecord r = new ProcessRecord(mAms, ai, processName, ai.uid);
-        r.setPid(mNextPid.getAndIncrement());
+            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 IApplicationThread thread = mock(IApplicationThread.class);
+        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();
@@ -239,13 +359,28 @@
         mRegisteredReceivers.put(r.getPid(), receiverList);
 
         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());
+
+        // 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()));
-            if (!wedged) {
+            final Bundle extras = invocation.getArgument(5);
+            if (!wedge) {
+                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+                assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
+                        != ProcessList.SCHED_GROUP_UNDEFINED);
                 mHandlerThread.getThreadHandler().post(() -> {
                     synchronized (mAms) {
-                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                                null, null, false, false);
+                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
+                                extrasOperator.apply(extras), abort, false);
                     }
                 });
             }
@@ -256,12 +391,16 @@
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
                     + Arrays.toString(invocation.getArguments()));
+            final Bundle extras = invocation.getArgument(4);
             final boolean ordered = invocation.getArgument(5);
-            if (!wedged && ordered) {
+            if (!wedge && ordered) {
+                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
+                assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
+                        != ProcessList.SCHED_GROUP_UNDEFINED);
                 mHandlerThread.getThreadHandler().post(() -> {
                     synchronized (mAms) {
                         mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                                null, null, false, false);
+                                null, extrasOperator.apply(extras), abort, false);
                     }
                 });
             }
@@ -272,21 +411,34 @@
         return r;
     }
 
-    private ApplicationInfo makeApplicationInfo(String packageName) {
+    static ResolveInfo makeManifestReceiver(String packageName, String name) {
+        return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
+    }
+
+    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, 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;
     }
 
@@ -301,16 +453,43 @@
     }
 
     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 ArgumentMatcher<Intent> filterEquals(Intent intent) {
+        return (test) -> {
+            return intent.filterEquals(test);
+        };
     }
 
     private ArgumentMatcher<Intent> filterEqualsIgnoringComponent(Intent intent) {
@@ -323,6 +502,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);
@@ -334,8 +524,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());
     }
 
@@ -346,18 +555,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;
@@ -366,6 +583,18 @@
         }
     }
 
+    static int getUidForPackage(@NonNull String packageName, int userId) {
+        return UserHandle.getUid(userId, getUidForPackage(packageName));
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        // To maximize test coverage, dump current state; we're not worried
+        // about the actual output, just that we don't crash
+        mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+                null, 0, true, null, false);
+    }
+
     /**
      * Verify dispatch of simple broadcast to single manifest receiver in
      * already-running warm app.
@@ -501,7 +730,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();
@@ -512,6 +744,43 @@
         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));
     }
 
     /**
@@ -520,7 +789,8 @@
     @Test
     public void testWedged() throws Exception {
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
-        final ProcessRecord receiverApp = makeActiveProcessRecordWedged(PACKAGE_GREEN);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+                ProcessBehavior.WEDGE);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
@@ -529,4 +799,445 @@
         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(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.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
+                    null, 0, true, null, false);
+        }
+
+        waitForIdle();
+        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);
+    }
 }
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/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 1a5d496..2df6823a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -98,6 +98,8 @@
     private DisplayPowerState mDisplayPowerStateMock;
     @Mock
     private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+    @Mock
+    private WakelockController mWakelockController;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -127,6 +129,12 @@
                     FloatProperty<DisplayPowerState> secondProperty) {
                 return mDualRampAnimatorMock;
             }
+
+            @Override
+            WakelockController getWakelockController(int displayId,
+                    DisplayPowerCallbacks displayPowerCallbacks) {
+                return mWakelockController;
+            }
         };
 
         addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
@@ -169,19 +177,15 @@
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
+        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+
 
         dpc.stop();
         advanceTime(1);
-
         // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
+        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
     }
 
     /**
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/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/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/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..96c3823 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -679,7 +679,7 @@
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
         startBackgroundUserAssertions();
-        verifyUserAssignedToDisplay(TEST_USER_ID1, UserManagerInternal.PARENT_DISPLAY);
+        verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
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..57ded99 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;
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..0bdcac6 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("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
     }
 
     @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("Asia/Shanghai", TIME_ZONE_CONFIDENCE_HIGH);
     }
 
     @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/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..fc2a4cf 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;
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..04702c4 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -27,7 +27,6 @@
 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;
@@ -44,7 +43,6 @@
 import java.nio.file.Path;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DisplayDeviceConfigTest {
     private DisplayDeviceConfig mDisplayDeviceConfig;
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 4075718..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,20 +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)
 
@@ -255,36 +313,32 @@
             .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)
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
-            eq(0.78f), anyLong())
+        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()
-        verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            anyInt(), eq(0.80f), anyLong())
+        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()
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
-            eq(0.80f), anyLong())
+        listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
     }
 
     @Test
@@ -294,28 +348,50 @@
         val listener = createMockListener()
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
-            eq(0.78f), anyLong())
+        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()
-        verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            anyInt(), eq(0.80f), anyLong())
+        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()
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            anyInt(), eq(0.80f), anyLong())
+        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()
-        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
-            anyInt(), eq(0.90f), anyLong())
+        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/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/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..6f3249e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -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/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 23a9013..ff838fd 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,7 @@
 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.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;
@@ -44,6 +45,7 @@
 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;
@@ -1024,7 +1026,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,6 +1112,7 @@
     static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
 
         private final TestState<String> mTimeZoneId = new TestState<>();
+        private int mTimeZoneConfidence;
         private ConfigurationInternal mConfigurationInternal;
         private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
         private ConfigurationChangeListener mConfigurationInternalChangeListener;
@@ -1141,18 +1144,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;
+        }
+
+        @Override
+        public void setDeviceTimeZoneAndConfidence(
+                String zoneId, @TimeZoneConfidence int confidence) {
             mTimeZoneId.set(zoneId);
+            mTimeZoneConfidence = confidence;
         }
 
         void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
@@ -1164,10 +1169,11 @@
             mTimeZoneId.assertHasNotBeenSet();
         }
 
-        void assertTimeZoneChangedTo(String timeZoneId) {
+        void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
             mTimeZoneId.assertHasBeenSet();
             mTimeZoneId.assertChangeCount(1);
             mTimeZoneId.assertLatestEquals(timeZoneId);
+            assertEquals(confidence, mTimeZoneConfidence);
         }
 
         void commitAllChanges() {
@@ -1280,20 +1286,22 @@
         }
 
         /** 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.assertTimeZoneChangedTo(
+                    suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
             mFakeEnvironment.commitAllChanges();
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
-            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+            mFakeEnvironment.assertTimeZoneChangedTo(
+                    suggestion.getZoneId(), TIME_ZONE_CONFIDENCE_HIGH);
             mFakeEnvironment.commitAllChanges();
             return this;
         }
@@ -1301,7 +1309,8 @@
         Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
             assertEquals("Only use this method with unambiguous geo suggestions",
                     1, suggestion.getZoneIds().size());
-            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneIds().get(0));
+            mFakeEnvironment.assertTimeZoneChangedTo(
+                    suggestion.getZoneIds().get(0), TIME_ZONE_CONFIDENCE_HIGH);
             mFakeEnvironment.commitAllChanges();
             return this;
         }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 2918365..107bbe1 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -79,7 +79,10 @@
         <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true" />
-        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
+        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
+                  android:theme="@style/WhiteBackgroundTheme"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"/>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml
new file mode 100644
index 0000000..6857ff99
--- /dev/null
+++ b/services/tests/wmtests/res/values/styles.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES 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="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+    </style>
+</resources>
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/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/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..170b388 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1532,7 +1532,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/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 0b58428..736f8f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.statusBars;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertNotNull;
@@ -23,20 +27,33 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+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;
-import android.view.WindowManager;
+import android.view.cts.surfacevalidator.BitmapPixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+import android.view.cts.surfacevalidator.SaveBitmapHelper;
 import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -45,6 +62,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -60,6 +78,8 @@
     private static final int BUFFER_HEIGHT = 100;
 
     private final Instrumentation mInstrumentation = getInstrumentation();
+    @Rule
+    public TestName mTestName = new TestName();
 
     @Rule
     public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -95,8 +115,8 @@
         buffer.unlockCanvasAndPost(canvas);
 
         t.show(secureSC)
-                .setBuffer(secureSC, buffer)
-                .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
+                .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
+                .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
                 .apply(true);
 
         ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -112,15 +132,70 @@
         Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
         screenshot.recycle();
 
-        int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
-                new PixelColor(PixelColor.RED));
-        long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
+        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+        Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
+        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+        int sizeOfBitmap = bounds.width() * bounds.height();
         boolean success = numMatchingPixels == sizeOfBitmap;
         swBitmap.recycle();
 
         assertTrue(success);
     }
 
+    @Test
+    public void testCaptureDisplay() throws Exception {
+        IWindowManager windowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        SurfaceControl sc = new SurfaceControl.Builder()
+                .setName("Layer")
+                .setCallsite("testCaptureDisplay")
+                .build();
+
+        SurfaceControl.Transaction t = mActivity.addChildSc(sc);
+        mInstrumentation.waitForIdleSync();
+
+        GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
+                PixelFormat.RGBA_8888,
+                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+                        | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+        Canvas canvas = buffer.lockCanvas();
+        canvas.drawColor(Color.RED);
+        buffer.unlockCanvasAndPost(canvas);
+
+        Point point = mActivity.getPositionBelowStatusBar();
+        t.show(sc)
+                .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
+                .setDataSpace(sc, DataSpace.DATASPACE_SRGB)
+                .setPosition(sc, point.x, point.y)
+                .apply(true);
+
+        Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
+        windowManager.captureDisplay(DEFAULT_DISPLAY, null, syncScreenCapture.first);
+        ScreenshotHardwareBuffer hardwareBuffer = syncScreenCapture.second.get();
+        assertNotNull(hardwareBuffer);
+
+        Bitmap screenshot = hardwareBuffer.asBitmap();
+        assertNotNull(screenshot);
+
+        Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+        screenshot.recycle();
+
+        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+        Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
+        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+        int pixelMatchSize = bounds.width() * bounds.height();
+        boolean success = numMatchingPixels == pixelMatchSize;
+
+        if (!success) {
+            SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
+        }
+        swBitmap.recycle();
+        assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
+                success);
+    }
+
     public static class ScreenshotActivity extends Activity {
         private static final long WAIT_TIMEOUT_S = 5;
         private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -130,7 +205,6 @@
             super.onCreate(savedInstanceState);
             getWindow().getDecorView().setPointerIcon(
                     PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
 
         SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -148,88 +222,14 @@
             }
             return t;
         }
-    }
 
-    public abstract static class PixelChecker {
-        static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
-            int numMatchingPixels = 0;
-            for (int x = 0; x < bitmap.getWidth(); x++) {
-                for (int y = 0; y < bitmap.getHeight(); y++) {
-                    int color = bitmap.getPixel(x, y);
-                    if (matchesColor(pixelColor, color)) {
-                        numMatchingPixels++;
-                    }
-                }
-            }
-            return numMatchingPixels;
-        }
+        public Point getPositionBelowStatusBar() {
+            Insets statusBarInsets = getWindow()
+                    .getDecorView()
+                    .getRootWindowInsets()
+                    .getInsets(statusBars() | displayCutout());
 
-        static boolean matchesColor(PixelColor expectedColor, int color) {
-            final float red = Color.red(color);
-            final float green = Color.green(color);
-            final float blue = Color.blue(color);
-            final float alpha = Color.alpha(color);
-
-            return alpha <= expectedColor.mMaxAlpha
-                    && alpha >= expectedColor.mMinAlpha
-                    && red <= expectedColor.mMaxRed
-                    && red >= expectedColor.mMinRed
-                    && green <= expectedColor.mMaxGreen
-                    && green >= expectedColor.mMinGreen
-                    && blue <= expectedColor.mMaxBlue
-                    && blue >= expectedColor.mMinBlue;
-        }
-    }
-
-    public static class PixelColor {
-        public static final int BLACK = 0xFF000000;
-        public static final int RED = 0xFF0000FF;
-        public static final int GREEN = 0xFF00FF00;
-        public static final int BLUE = 0xFFFF0000;
-        public static final int YELLOW = 0xFF00FFFF;
-        public static final int MAGENTA = 0xFFFF00FF;
-        public static final int WHITE = 0xFFFFFFFF;
-
-        public static final int TRANSPARENT_RED = 0x7F0000FF;
-        public static final int TRANSPARENT_BLUE = 0x7FFF0000;
-        public static final int TRANSPARENT = 0x00000000;
-
-        // Default to black
-        public short mMinAlpha;
-        public short mMaxAlpha;
-        public short mMinRed;
-        public short mMaxRed;
-        public short mMinBlue;
-        public short mMaxBlue;
-        public short mMinGreen;
-        public short mMaxGreen;
-
-        public PixelColor(int color) {
-            short alpha = (short) ((color >> 24) & 0xFF);
-            short blue = (short) ((color >> 16) & 0xFF);
-            short green = (short) ((color >> 8) & 0xFF);
-            short red = (short) (color & 0xFF);
-
-            mMinAlpha = (short) getMinValue(alpha);
-            mMaxAlpha = (short) getMaxValue(alpha);
-            mMinRed = (short) getMinValue(red);
-            mMaxRed = (short) getMaxValue(red);
-            mMinBlue = (short) getMinValue(blue);
-            mMaxBlue = (short) getMaxValue(blue);
-            mMinGreen = (short) getMinValue(green);
-            mMaxGreen = (short) getMaxValue(green);
-        }
-
-        public PixelColor() {
-            this(BLACK);
-        }
-
-        private int getMinValue(short color) {
-            return Math.max(color - 4, 0);
-        }
-
-        private int getMaxValue(short color) {
-            return Math.min(color + 4, 0xFF);
+            return new Point(statusBarInsets.left, statusBarInsets.top);
         }
     }
 }
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 3331839..51894f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -735,6 +735,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.
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 46b4b76..c714f01 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;
@@ -41,6 +42,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -48,26 +50,34 @@
 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;
 import android.window.ClientWindowFrames;
+import android.window.ScreenCapture;
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
@@ -85,6 +95,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowManagerServiceTests extends WindowTestsBase {
+
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
@@ -319,14 +330,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,
@@ -335,14 +354,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,
@@ -350,6 +377,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();
@@ -423,6 +527,45 @@
                 LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
     }
 
+    @Test
+    public void testCaptureDisplay() {
+        Rect displayBounds = new Rect(0, 0, 100, 200);
+        spyOn(mDisplayContent);
+        when(mDisplayContent.getBounds()).thenReturn(displayBounds);
+
+        // Null captureArgs
+        ScreenCapture.LayerCaptureArgs resultingArgs =
+                mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */);
+        assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+        // Non null captureArgs, didn't set rect
+        ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build();
+        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+        assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+        // Non null captureArgs, invalid rect
+        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                .setSourceCrop(new Rect(0, 0, -1, -1))
+                .build();
+        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+        assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+        // Non null captureArgs, null rect
+        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                .setSourceCrop(null)
+                .build();
+        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+        assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+        // Non null captureArgs, valid rect
+        Rect validRect = new Rect(0, 0, 10, 50);
+        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                .setSourceCrop(validRect)
+                .build();
+        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+        assertEquals(validRect, resultingArgs.mSourceCrop);
+    }
+
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
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/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 91b868d..be7c112 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,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;
@@ -309,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
@@ -809,8 +796,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);
         }
 
@@ -818,8 +807,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) {
@@ -1917,7 +1907,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) {
@@ -2031,6 +2020,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
@@ -2041,9 +2035,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;
                     }
                 }
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 4230225..7b7c83f 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -1107,7 +1107,8 @@
     }
 
     /**
-     * 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.
      */
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/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/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/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/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/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/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 4d801c90..dc49fc8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -19,11 +19,13 @@
 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.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
 
@@ -173,14 +175,32 @@
      */
     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"
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..31fcc6a 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
@@ -26,11 +26,9 @@
 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
@@ -113,30 +111,11 @@
 
     @Presubmit
     @Test
-    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
+    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
 
     @Presubmit
     @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
@@ -148,21 +127,7 @@
 
     @Presubmit
     @Test
-    fun imeLayerIsBecomesVisibleLegacy() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.assertLayers {
-            this.isVisible(ComponentNameMatcher.IME)
-                    .then()
-                    .isInvisible(ComponentNameMatcher.IME)
-                    .then()
-                    .isVisible(ComponentNameMatcher.IME)
-        }
-    }
-
-    @Presubmit
-    @Test
-    fun imeLayerBecomesVisibleShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
+    fun imeLayerBecomesVisible() {
         testSpec.assertLayers {
             this.isVisible(ComponentNameMatcher.IME)
         }
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..fc4e6ea 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,8 +16,6 @@
 
 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
@@ -25,8 +23,10 @@
 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
@@ -47,6 +47,11 @@
 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() = {
@@ -67,105 +72,6 @@
             }
         }
 
-    /** {@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.
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..9e27106
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 77f28f6..c5ead06 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
@@ -74,7 +74,7 @@
                     .withFullScreenApp(testApp)
                     .waitForAndVerify()
                 testApp.postNotification(wmHelper)
-                tapl.goHome()
+                device.pressHome()
                 wmHelper.StateSyncBuilder()
                     .withHomeActivityVisible()
                     .waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index 4f21412..d362c7d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -31,11 +31,13 @@
 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
@@ -86,6 +88,7 @@
     @Presubmit
     @Test
     fun testSimpleActivityIsShownDirectly() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             isVisible(ComponentNameMatcher.LAUNCHER)
                 .isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml
deleted file mode 100644
index 7c11984..0000000
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.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.
-  -->
-<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/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 9583f97..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
@@ -177,12 +177,6 @@
         }
     }
 
-    public static class SendNotificationActivity {
-        public static final String LABEL = "SendNotificationActivity";
-        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
-                FLICKER_APP_PACKAGE + ".SendNotificationActivity");
-    }
-
     public static class Bubbles {
         public static class LaunchBubble {
             public static final String LABEL = "LaunchBubbleActivity";
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/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
deleted file mode 100644
index 8868488..0000000
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/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.server.wm.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/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/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/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7efe3c3..7ddbe95 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -130,7 +130,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 +161,7 @@
         "ApkInfo.proto",
         "Configuration.proto",
         "Resources.proto",
+        "ResourceMetadata.proto",
         "ResourcesInternal.proto",
         "ValueTransformer.cpp",
     ],
@@ -218,6 +219,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/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index e37c2d4..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;
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 10b84b0c..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.
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/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/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